退役狗在刷程書的過程中看到了一個有趣的視頻, 講解了一個有趣的問題.c++
在網上隨便搜索了一下竟然還真的找到了一道以它爲背景的OI題目, BZOJ1041.算法
下面的內容會首先回顧一下視頻所討論的知識, 有了這些知識, 天然就明白應該怎麼去作這道題了.ide
另外, 這道題在網路上的解法大可能是暴力求解, 這篇博客也提出了本題一個複雜度爲\(\mathcal{O}(分解質因數)\)的解決方案.ui
首先介紹收斂於\(\pi\)的一個無窮級數:spa
\[\pi = \lim_{N \rightarrow \infty} \frac 4N \sum_{i=1}^N \sum_{d|i}\chi(d)\]code
怎麼證明它呢? 一種常見方法是使用Calculus, 另一種就是使用數論方法.get
Consider有一個網格, 我們畫出一個半徑爲\(r\)的圓:博客
\[x^2+y^2=r^2\]it
那麼, 若是我們能夠數出來這裏面有多少個點, 我們就能夠間接計算出\(pi\)的值.模板
我們把這個圓分紅若干個小圓, 假設每個圓的方程是:
\[x^2+y^2=n\]
我們能夠枚舉全部可能的\(n\)來確定圓中有多少個格點.
考慮到格點的\(x, y\)都是integer, 那麼我們只須要去枚舉全部小於\(r^2\)的整數\(n\)就能夠了.
那麼現在問題就轉化成:
給定一個正整數\(n\), 有多少個pair\((x, y)\)使得\(x^2+y^2=n\)成立?
考察\(n\)的任意一個分解\(n = x^2+y^2\), 若存在這樣一個分解, 這樣的分解也能夠寫成:
\[n = (x+y\mathrm{i})(x-y\mathrm{i})\]
考慮\(n\)的質因數分解:
\(n = \prod p_i^{a_i}\)
對於其中的一個素數, 我們不加證明地給出幾個結論:
然後, 我們能夠對幾種因子做分類討論:
若出現這樣的情形, 在不考慮乘以\(1, -1, i, -i\)的情況下, 我們能夠將這分解出來的\(2a_i\)個複數隨意組合相乘, 能夠證明獲得的兩個共軛複數不一樣的情形有\(a_i+1\)種.
這樣的素數叫作高斯素數. 若出現這樣的情形, 若是\(a_i\)是偶數, 我們能夠在以前獲得了的兩個共軛複數上都乘以\(p_i^{a_i/2}\)而不影響結果. 否則, 方案數就是0.
若出現這樣的情形, 理論上來說2能夠分解成\(2 = (1+i)(1-i)\), 可是有\((1-i)*i = (1+i)\), 從而無論怎麼分組, 都會與最終的乘以\(1, -1, i, -i\)相重複, 因此對於方案數目的貢獻只有\(\times 1\)
經過上面的討論, 我們就獲得了計數拆分\(n = x^2+y^2\)方案的一個方法. 具體的方案與代碼在下面的題解中給出.
到現在, 這個結果已經蠻不錯, 可是我們還能夠作的更優美.
定義函數:
\[ \begin{eqnarray}\chi(x)= \begin{cases} 1, &x \mod 4 = 1\cr 0, &x \mod 2 = 0 \cr -1, &x \mod 4 = 3\end{cases} \end{eqnarray} \]
這個函數是一個積性函數.
通過\(\chi(x)\)定義中的分類討論, 在求解的時候, 我們便沒必要再分類討論了.
這樣, 我們要求得的答案就能夠寫成:
\[\mathcal{ans} = 4 \times \prod \sum_{0 \leqslant i \leqslant a_i} \chi(p_i^i)\]
考慮到上述函數是一個積性函數, 我們有:
\[ans = 4 \times \sum_{i=1}^N \sum_{d|i}\chi(d)\]
對上述函數稍做整理, 並令\(\lim_{n \rightarrow \infty}\)近似, 就獲得了要證的式子.
\[\mathcal{Q.E.D}\]
本題是一個上面結論的退化形式, 令\(n = r^2\), 直接應用算法便可.
下面是代碼.
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll xjb=10; ll mmul(ll a, ll b, ll m){ ll d=((long double)a/m*b+1e-8); ll r=a*b-d*m; return r<0?r+m:r; } ll mpow(ll a, ll b, ll m){ll r=1;for(;b;b>>=1,a=mmul(a,a,m))if(b&1)r=mmul(r,a,m);return r;} ll gcd(ll a, ll b){return a?gcd(b%a,a):b;} ll prime(ll n){ if(n==1) return 0; if(n==2||n==3||n==5) return 1; if(!(n&1)||(n%3==0)||(n%5==0)) return 0; ll m=n-1; ll k=0; while(!(m&1)) m>>=1, k++; for(ll tt=0; tt<xjb; ++tt){ ll x=mpow(rand()%(n-2)+2,m,n), y=x; for(ll i=0; i<k; ++i){ x=mmul(x,x,n); if(x==1&&y!=1&&y!=n-1) return 0; y=x; } if(x!=1) return 0; } return 1; } ll f[105]; ll M; ll rho(ll n, ll c){ ll x=rand()%n, y=x, t=1; for(ll i=1, k=2; t==1; ++i){ x=(mmul(x,x,n)+c)%n; t=gcd(x>y?x-y:y-x, n); if(i==k) y=x, k<<=1; } return t; } void work(ll n){ if(n==1) return; if(prime(n)){f[M++]=n; return;} ll t=n; while(t==n) t=rho(n, rand()%5+1); work(t); work(n/t); } int main(){ srand(19260817); ll n; scanf("%lld", &n); n*=n; M=0; work(n); sort(f, f+M); ll ans = 1; for(ll i=0, c=1; i<M; ++i){ if(f[i]!=f[i+1]) { // cout << f[i] << ' ' << c << endl; if(f[i] == 2) { } else if(f[i] % 4 == 1) { ans *= c+1; } else { if(c % 2 == 1) ans = 0; } c = 1; } else c++; } printf("%lld\n", ans*4); return 0; }
退役老年選手碼力降低太多了啊....告別OI以後AC的第一道題...感覺還是OI比較美好啊...惋惜再也回不去了.
樸素的根號分解過不去這個題, 從網上找了一個Pollard-Rho模板改了改AC了這個題.
又頹廢了一個下午....還有100天就高考了....這樣下去是要gg啊...