UVA 106 - Fermat vs. Pythagoras

UVA 106 - Fermat vs. Pythagoras

題目敘述

輸入 N (1 ≤ N ≤ 1000000),求X² + Y² = Z²(0 < X < Y < Z ≤ N)的解中

  • 三數互質 gcd(X, Y, Z) = 1 的解有幾組
  • 1 ~ N 中有幾個正整數沒被任何一組解包含

解法

原本想枚舉 Y 和 Z,但時間上會是 10¹¹ 等級,顯然不是一個好方法。

這時,突然想到了「畢式三元數」(Pythagorean triple),畢竟每次數學培訓上數論都會講到,前幾天翻數學競賽的書也剛好看到,不過似乎只針對「互質畢式三元數」討論而已。

「互質畢式三元數」性質如下:

X, Y, Z ∈ 𝚴,X² + Y² = Z² 且 gcd(X, Y, Z) = 1,則可表示為

  • X = m² - n²
  • Y = 2mn
  • Z = m² + n²

其中 m > n,gcd(m, n) = 1,m, n 為一奇一偶。

這題的範圍不僅僅限於 gcd(X, Y, Z) = 1, 但只要將每組「畢式三元數」除以 gcd(X, Y, Z),就都能變成「互質畢式三元數」了。換句話說,「畢式三元數」一定是由某一組「互質畢式三元數」乘k倍而來。 表示為 X = k (m² - n²),Y = 2kmn,Z = k (m² + n²)


考慮 m, n 的範圍,1000000 ≥ N ≥ Z = m² + n²,得 1000 ≥ √N ≥ m > n ≥ 1。 枚舉 m, n的時間為 10⁶ 等級,明顯能快很多。

我的方法是跑一層 for 枚舉 m,裡面再跑一層 for 枚舉 n,if(gcd(m, n) == 1),則跑一層 k。

寫完後,丟到 UVA 上排名 21,時間 0.050。 (第一名 0.008,我超好奇怎麼辦到?) 天啊!這真的怪爆了,我到底哪裡比別人快啊?於是 google 了別人的解法。

看到大部分的人都是對每筆輸入 N 跑一次 for m in range(1,sqrt(N)),這方法我沒想到……因為我寫 DP 寫中毒了,於是動不動就想直接給他一次找完,也就是直接「建表」。


這題的數論難關解決後,建表的過程確實有點卡,於是就來記一下重點部分。

大部分人是直接開個 bool 紀錄數字出現過了沒,還有開個變數紀錄有幾組互質的。

首先,來談互質的組數 p。用 DP 的角度切入,p[i] 和 p[i-1] 有關嗎?

p[i] = p[i - 1] + (z == i 的互質組數)。 所以就開個 cnt 陣列紀錄,每找到一組互質畢式三元數,cnt[Z]++。p[N]就是cnt[N]的前綴和,O(N)一次做完即可。

接著,是算有幾個沒用過的數。1 ~ i + 1 會比 1 ~ i 多一個數能用,而 1 ~ i 用過的數 1 ~ i + 1 也同樣被用過。num[i + 1] = num[i] + 1 - (有幾個數在 Z == i + 1 時才出現)。所以重點是計算有幾個數在 Z = i + 1 時第一次出現,也就是紀錄每個數搭配的 Z 的最小值,最後在掃過一遍就行了。

因為都是由互質三元數去找其他三元數,所以不會找到重複的,所以效率佳。寫這篇時發現自己沒有應用到一奇一偶的性質,改成 n += 2 後,丟到UVA上排名 13,時間 0.040。

Code

 1#include<bits/stdc++.h>
 2using namespace std;
 3#define MAX 1000001
 4int f[MAX], num[MAX], p[MAX], ans[MAX] = {0, 1};
 5inline bool coprime(int x, int y, int z) {
 6    return __gcd(__gcd(x, y), z) == 1;
 7}
 8int main() {
 9    fill(f, f + MAX, MAX);
10    for (int m = 2; m < 1000; m++)
11        for (int n = (m & 1) + 1; n < m; n += 2) {
12            if (__gcd(m, n) != 1) continue;
13            int x = m * m - n * n, y = 2 * m * n, z = m * m + n * n;
14            if (z >= MAX) break;
15            if (coprime(x, y, z)) {
16                p[z]++;
17                for(int k = MAX / z; k >= 1; k--) {
18                    int xx = x * k, yy = y * k, zz = z * k;
19                    f[xx] = min(f[xx], zz);
20                    f[yy] = min(f[yy], zz);
21                    f[zz] = zz;
22                }
23            }
24        }
25    for(int i = 1; i <= MAX; i++)
26        p[i] += p[i - 1], num[f[i]]++;
27    for (int i = 2; i <= MAX; i++)
28        ans[i] = ans[i - 1] + 1 - num[i];
29    int N;
30    while (scanf("%d", &N) == 1) printf("%d %d\n", p[N], ans[N]);
31    return 0;
32}
Licensed under CC BY-NC-ND
Built with Hugo
Theme Stack designed by Jimmy