【整理】分塊在數論中的運用(初稿)(各位幫幫忙填下坑,裏面列的好多都不會)
之前已經做過幾個分塊的水題,導致nmphy居然口出狂言:“高中學過,簡單”。(現在nmphy收回他的話,並且跪著寫下這篇總結)
前言:
- 由於數論模運算中常常遇到一些問題:比如long long溢出,需要把乘法改為快速乘法(和快速冪是一個道理)。
- 註意邊界,0,1,什麽的。
- 運用函數時防止需要精度誤差,如sqrt,log2()等等(因為這個,昨天的CF第二題第48個點就被hack了,可以去感受CF456B一下)
- 。。。
α,組合數取模: 組合數取模根據n,m,p規模不同,分別有不同的解決方法。
- 數據小的就不說了,暴力也行,唯一分解
- n,m <= 10^100,p <= 10^5,p是素數 。Lucas定理
- n,m <= 10^100,p <= 10^5,p是合數,且分解後因子不太多。 Lucas定理+中國剩余定理。
- n,m <= 10^100,p <= 10^9, p是素數,好像涼涼。
第四個這怎麽搞呢?這個時候……貌似跟3差不多。但是……需註意一個問題, c(n % p,m % p)我們在做Lucas 定理的時候需要預處理(得到階乘),此時P也很大, 預處理不出來。方法:分塊打表
β,階乘除階乘:
1:(n! / m!)% p 怎麽求, n,m <= 10^10,p <= 10^6,p是素數 。
顯然不能用Lucas,畢竟不是組合數。再看n,m是如此的大,怎麽搞? 這個我們轉化成乘法逆元來做
- ) 令 n! = (p^a) * u, m! = (p^b) * w
- ) 顯然a >= b。 若a > b 此時 n! / m! 是p的倍數。 那麽余數為0。
- ) 若a == b,此時我們只需要算u / w。由於w與p互質,u / w可以直接算u*w的逆元。
- ) 現在開始討論如何計算u/w n! = 1 * 2 * … * (p – 1) * 1p * (p + 1) * (p + 2) * … (2p – 1) * 2p * … (kp + 1) * (kp + 2) * .. (kp – 1) * kp * … ((k +1)p + 1) * ((k + 1)p + 2) * .. ((k +1)p + t)
其中,K = n/ p, t = n!% p
註意到 1和(p + 1) , 2和(p+2)…都是同余的。 所以我們的式子可以化成: ((p – 1)!)^k * t! * k! * p^k,k!哪裏出現的?
註意到 1p,2p..kp P^k我們提取出來,(p – 1)!和t!可以預處理,現在需要處理的是k!即(n/p)!,所以我們現在問 題轉化成計算k!,那麽遞歸下去即可。
2: 現在講講n,m <= 10^9,p <= 10^6, p不是素數怎麽做
首先,我們應該把p分解成pi^ci的形式。 直奔主題,討論如何把n!分解掉。
首先,我們把1..n中p的倍數提取出來,那麽由於mod p^c. 所以提取後會有若幹段循環:1 * 2 * … * (p – 1) * (p + 1) * (p + 2) *… *(p^c – 1) 令k = n / (p^c), t = n % (p^c),kk = n / p N!= P[p^c – 1]^k * P[t] * p^kk * kk! 註意這裏的P[p^c-1]是沒有把p的倍數乘進來。
同上,只要遞歸計算kk!(相關題目:spoj sequence)
γ,高次剩余問題:
a^x = b(mod p)
- 知道a,x,p,求b (100%有解)
- 知道a,b,p,求x (有可能無解)
- 知道x,b,p,求a (依然有可能無解)
- 知道a,x,p,求某區間範圍內的p(持續有可能無解)
對2,3類問題我們還可以分別進行升級,求解的個數…) 從1到4,求解的難度是越來越大的.
這裏只講第二個和分塊的關系:BSGS算法。
BSGS算法(北上廣深?拔山蓋世?): (參考,http://blog.csdn.net/Clove_unique/article/details/50740412)
先看假如暴搜,其枚舉範圍:
根據費馬小定理:a^(p−1)≡1(modp)。
如果x已經枚舉到了p-1了,繼續枚舉的話就會產生循環。
所以,在暴搜中x的枚舉範圍就是0……p-1。
試想分塊如何優化暴搜:
我們想一想可不可以用分塊來解決枚舉的x。
把x分成p−1−−−−√分別枚舉行不行?
設m=p−1−−−−√,y=a∗m-b,這樣枚舉a和b就相當於分塊枚舉了。
那麽現在就變成了x^(a∗m-b)≡z(modp)
把a和b分別放在兩邊:x^b*z≡x^(a∗m)(modp)
我們可以發現左邊的x^b最多只有m個,完全可以預處理出來放進hash裏面。
第一代醜代碼://4000多ms,主要是用map來哈希,根本沒有體現到BSGS以空間換時間的優勢。
#include<cstdio> #include<cmath> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<map> #define ll long long using namespace std; ll a,b,p; map<ll,int>mp; void solve() { if(a%p==0) { printf("no solution\n");return ; } b%=p; a%=p; ll tmp=b; ll block=sqrt(p-1)+1;//向上取整 if(mp[tmp]==0) mp[tmp]=0; for(int j=1;j<=block;j++){ tmp=tmp*a%p; mp[tmp]=j; } ll tmpa=1,m=block; while(m){ if(m&1) tmpa=tmpa*a%p; a=a*a%p; m/=2; } ll tmpb=1; for(int i=1;i<=block;i++){ tmpb=tmpb*tmpa%p; if(mp[tmpb]!=0){ printf("%lld\n",((i*block-mp[tmpb])%p+p)%p); return ; } } printf("no solution\n"); return ; } int main() { while(~scanf("%lld%lld%lld",&p,&a,&b)){ mp.clear(); solve(); } return 0; }View Code
修進後的模板代碼://30ms
#include<cstdio> #include<cstring> #include<cmath> #define ll long long const int maxn=76543; int To[maxn],Laxt[maxn],Next[maxn],id[maxn],cnt; void insert(int x,int y){ int k=x%maxn; Next[++cnt]=Laxt[k]; Laxt[k]=cnt; id[cnt]=y; To[cnt]=x; } int find(int x){ int k=x%maxn; for(int i=Laxt[k];i;i=Next[i]){ if(To[i]==x) return id[i]; } return -1; } int BSGS(int a,int b,int p){ memset(Laxt,0,sizeof(Laxt));cnt=0; if(a%p==0||b%p==0) return -1; if(b==1) return 0; int m=sqrt(1.0*p)+1,j; ll x=1,np=1; for(int i=0;i<m;i++,np=np*a%p) insert(np*b%p,i); for(ll i=m;;i+=m){ if((j=find(x=x*np%p))!=-1) return i-j; if(i>p) break; } return -1; } int main() { int a,b,p; while(~scanf("%d%d%d",&p,&a,&b)){ int ans=BSGS(a,b,p); if(ans==-1) printf("no solution\n"); else printf("%d\n",ans); } return 0; }View Code
先放道題在這裏,https://vjudge.net/problem/CodeForces-830C ,之前遇到的,不知道和這個有關沒,等我肝完博客在看看。
【參考】: 成都七中 2013級13班 王 迪 《信息學中的分塊思想》
【整理】分塊在數論中的運用(初稿)(各位幫幫忙填下坑,裏面列的好多都不會)