[SMOJ2078]勾股數

解法一:
顯然可以考慮直接在n的範圍內暴力枚舉 abc 並進行判斷統計,時間複雜度爲 O(n3)

解法二:
注意到 c2 是兩個完全平方數(其算術平方根恰好爲整數的數)之和,且其自身也是一個完全平方數。則可以枚舉 ab ,判斷是否爲整數即可。時間複雜度爲 O(n2)

解法三:
在解法一、二中,我們其實作了大量無用的枚舉。
不難發現,對於某個確定的 a ,能與其配對成爲勾股數組的 (b,c) 對數其實非常少,它們滿足一些特殊的性質。能不能利用它們之間的關係,減少無用的枚舉呢?
可以將 a2+b2=c2 移項後得到 a2=c2b2 ,利用平方差公式進行因式分解後得到 a2=(c+b)(cb) 。這時可以發現,c+bcb 都是 a2因數
因數有什麼用?事實上,109 的範圍內數字的因數個數相當少,最多不會超過 1000個。
這樣一來,對於一個確定的 a2 ,如果能夠枚舉它的某個因數 x ,就能得到與其配對的另一個因數 y=a2x 。不妨規定 x<y ,則對應上式,可得

{c+b=ycb=x

解這個方程組,得

{b=yx2c=y+x2

顯而易見,要使 bc 爲整數,xy 的奇偶性應該相同,且算得的 bc 不得大於 n 。也就意味着,只要能枚舉出 a2 的因數,就可以很快確定能與其配成勾股數組的 (b,c) 對數。
則問題轉化爲:如何快速枚舉 a2 的因數?這裏就要用到質因數分解的相關知識。
回憶一下,篩法求質數表的過程,可以發現每個合數第一次都是被自己最小的一個質因數篩掉的。例如:12=22×31 ,最早是被 2 篩掉的。不妨記 i 的最小質因數爲 mindivi ,則當且僅當 i 爲質數時,mindivi=i ,否則爲第一個把自己篩掉的質數。
利用篩法時順便記錄的最小質因數,就可以求得質因數分解的結果。例如,將12 除以其最小質因數 2 時(此時已知 12=21× ),變成 6,mindiv6=2 ,再除以 2(此時已知 12=22× ),變成 3,發現 mindiv3=3 就是自己。說明到了質數,分解結束。這樣就得到了 12 分解質因數的結果 22×31 。容易證明,一個數 n 被這樣分解不會超過 log2n 步,因此很快。
顯而易見的是,根據冪的乘方,將 a 的質因數分解結果 xk11×xk22××xkmm ,對其每個不同質因數的指數乘 2 得到的結果 x2k11×x2k22××x2kmm ,就是 a2 分解質因數的結果。
於是就解決了分解質因數的問題,最後一步:怎麼用分解得到的質因數枚舉因數?
很顯然一個數的每個因數都是其若干質因數之積,因此只需枚舉每個質因數選取的指數 ki 即可,質因數 xi 可被乘上 0 到 ki 次貢獻給因數,即一共可以得到 (ki+1) 個不同的因數。
到這裏,所有的難關都被攻克,本題也就迎刃而解了。只需稍加註意細節,即可 AC。

參考代碼:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int MAXN = 1e6 + 100;

long long N, mindiv[MAXN], ans;
int div_cnt;
pair <long long, int> divisors[1000];

void dfs(int k, long long tot, long long a) { //k 爲已考慮的質因數個數,tot 爲當前乘積(最終爲所枚舉的 c-b)
    if (tot >= a) return ; //得到的必須是正常的三角形,滿足三角形不等式 c-b<a
    if (k > div_cnt) { //都考慮完了,得到了 a^2 的某個因數
        long long tot1 = a * a / tot; //c+b
        if ((tot ^ tot1) & 1) return ; //奇偶性要相同,否則得到的 b、c 爲分數
        long long b = (tot1 - tot) >> 1, c = (tot1 + tot) >> 1;
        ans += b > a && b <= N && c <= N; //範圍限制
        return ;
    }
    long long mult = 1;
    for (int i = 0; i <= divisors[k].second; i++) { //質因數 x[i] 可被乘上 0 到 k[i] 次貢獻給因數
        dfs(k + 1, tot * mult, a);
        mult *= divisors[k].first;
    }
}

int main(void) {
    freopen("2078.in", "r", stdin);
    freopen("2078.out", "w", stdout);
    scanf("%lld", &N);
    for (long long i = 2; i <= N; i++) //篩
        if (!mindiv[i])
            for (long long j = i; j <= N; j += i) if (!mindiv[j]) mindiv[j] = i;
//  for (int i = 2; i <= N; i++) printf("%d ", mindiv[i]);
    for (long long i = 3; i < N; i++) {
//      printf("%I64d\n", i);
        divisors[div_cnt = 0] = make_pair(0, 0); //根據篩法結果分解質因數
        for (long long t = i; t != 1; t /= mindiv[t]){
            if (mindiv[t] == divisors[div_cnt].first) ++divisors[div_cnt].second; //first 爲具體的質因數,second 爲指數
            else divisors[++div_cnt] = make_pair(mindiv[t], 1); //分解過程中得到的質因數一定是從小到大的,想一想,爲什麼
        }
        for (int j = 1; j <= div_cnt; j++) divisors[j].second <<= 1; //a 分解質因數的結果,指數乘 2 就得到 a^2 分解質因數的結果
//      printf("%d=%d^%d", i, divisors[1].first, divisors[1].second);
//      for (int j = 2; j <= div_cnt; j++) printf("*%d^%d", divisors[j].first, divisors[j].second);
//      putchar('\n');
        dfs(1, 1, i); //枚舉 c-b
    }
    printf("%d\n", ans);
    return 0;
}


總結一下,這題其實蘊含着一種非常巧妙的轉化思想,通過對式子進行適當變形,找到不一樣的角度來考慮和解決問題。這種思想,在 NOIp2015 和 NOIp2016 中都有所考察,也是 OI 競賽中非常重要的一部分,需要對問題進行觀察、分析,有深入的認識,才能想出。

發佈了196 篇原創文章 · 獲贊 19 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章