之前已经做过几个分块的水题,导致nmphy居然口出狂言:“高中学过,简单”。(现在nmphy收回他的话,并且跪着写下这篇总结)
前言:
- 由于数论模运算中常常遇到一些问题:比如long long溢出,需要把乘法改为快速乘法(和快速幂是一个道理)。
- 注意边界,0,1,什么的。
- 运用函数时防止需要精度误差,如sqrt,log2()等等(因为这个,昨天的CF第二题第48个点就被hack了,可以去感受CF456B一下)
- 。。。
α,组合数取模: 组合数取模根据n,m,p规模不同,分别有不同的解决方法。
- 数据小的就不说了,暴力也行,唯一分解也o98k。
- 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也很大, 预处理不出来。方法:分块打表。只保存sqrt(p)个 n!的值,c(n,m) = n! / (m! * (n – m)!),P是素数,可以直接乘法逆元。所以时间就是 logP * sqrt(P)。具体的博主日后补充。(毕竟nmphy也没写过)
β,阶乘除阶乘:
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; }
修进后的模板代码://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; }
先放道题在这里,https://vjudge.net/problem/CodeForces-830C ,之前遇到的,不知道和这个有关没,等我肝完博客在看看。
【参考】: 成都七中 2013级13班 王 迪 《信息学中的分块思想》