逆元
对于正整数和,如果有,那么把这个同余方程中的最小正整数解叫做模的逆元。
逆元一般用扩展欧几里得算法来求得,如果为素数,那么还可以根据费马小定理得到逆元为。
推导过程如下
还有一个公式:a/b mod m=a mod bm/b;
3.线性求逆元
其实有些题需要用到模的所有逆元,这里为奇质数。那么如果用快速幂求时间复杂度为,
如果对于一个1000000级别的素数,这样做的时间复杂度是很高了。实际上有的算法,有一个递推式如下
它的推导过程如下,设,那么
对上式两边同时除,进一步得到
再把和替换掉,最终得到
初始化,这样就可以通过递推法求出模奇素数的所有逆元了。
1.hdu1576 http://acm.hdu.edu.cn/showproblem.php?pid=1576
A/B
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5597 Accepted Submission(s):
4371
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。
/* 扩展欧几里得求逆元 满足a*x 9973 b==1 9973 b 则x为a的逆元 A/B=A*(B的逆元) */ #include<iostream> #include<cstdio> #include<cstring> #define LL long long using namespace std; LL A,B,a,b,n,d,x,y; LL exgcd(int a,int b) { if(a==0&&b==0) return -1; if(b==0) { x=1;y=0;d=a; return d; } d=exgcd(b,a%b); LL tmp=x;x=y;y=tmp-a/b*y; return d; } LL inv(LL a,LL b) { d=exgcd(a,b); if(d==1)//a,b 互素 return (x%9973+9973)%9973; else return -1; } int main() { int T;cin>>T; while(T--) { scanf("%lld%lld",&n,&B); LL p=inv(B,9973); printf("%d\n",n*p%9973); } return 0; }
1.poj 1845 http://poj.org/problem?id=1845
Time Limit: 1000MS | Memory Limit: 30000K | |
Total Submissions: 21208 | Accepted: 5339 |
Description
Input
Output
Sample Input
2 3
Sample Output
15
Hint
The natural divisors of 8 are: 1,2,4,8. Their sum is 15.
/* 题意:求A^B 所有约数(因子)之和mod 9901。 应用质因数分解+约数和公式+逆元+等比数列求和公式 A=p1^k1*p2^k2*...pn^kn A^B=p1^(k1*B)*p2^(k2*B)...pn^(kn*B) 约数和公式:Sum=(1+p1+p1^2+...+p1^k1)*(1+p2+p2^2+...+p2^k2)*(......pk^kn) Sum(A^B)=(1+p1+p1^2+...+p1^k1*B)*(1+p2+p2^2+...+p2^k2*B)*(......pk^kn*B) mod 9901 对于每一个 (1+p1+p1^2+...+p1^k1*B),根据等比数列求和公式为 [1-p1^(1+k1*B)]/(1-p1) mod 9901 根据逆元公式:a/b mod m=(a mod mb )/b 原式= [p1^(k1*B+1)-1] mod [9901*(p1-1)] / (p1-1) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 10101 #define mod 9901 #define LL long long using namespace std; long long A,B; int cnt; int c[maxn],f[maxn],p[maxn],factor[maxn]; void Eprime()//素数表 { f[0]=f[1]=1; for(int i=1;i<=maxn;i++) if(f[i]==0) for(int j=i+i;j<=maxn;j+=i) f[j]=1; for(int i=1;i<=maxn;i++) if(f[i]==0) p[cnt++]=i; } /*void Devide(int x) { for(int i=1;i<=maxn;i++) { int P=p[i];if(x==1)break; while(x%P==0) { x/=P; c[++cnt]=P; } } for(int i=1;i<=cnt;i++) factor[c[i]]=1; for(int i=1;i<=cnt;i++) while(c[i]==c[i+1]) factor[c[i]]++; sort(factor+1,factor+cnt); sort(c,c+cnt); num=unique(factor,factor+cnt)-factor; num=unique(c+1,c+cnt)-c; num--; for(int i=1;i<=num;i++) factor[i]*=B; } */ LL Mul(LL a,LL b,LL m)//慢速乘,防止乘爆了 { LL res=0;a%=m; while(b) { if(b & 1) res=(res+a)%m; b>>=1; a=(a+a)%m; } return res; } LL fast(LL a,LL b,LL m)//快速幂 { LL res=1;a%=m; while(b) { if(b&1) res=Mul(res,a,m); b>>=1; a=Mul(a,a,m); } return res; } void Devide(LL A,LL B) { LL ans=1; for(int i=0;p[i]*p[i]<=A;i++) { int P=p[i]; if(A%p[i]==0) { int num=0; while(A%P==0) { num++; A/=P; } //原式= [p1^(k1*B+1)-1] mod [9901*(p1-1)]/(p1-1) LL M=(P-1)*mod; ans*=(fast(P,num*B+1,M)+M-1)/(P-1); ans%=mod; } } if(A>1)//A是素数 { LL M=(A-1)*mod; ans*=(fast(A,B+1,M)+M-1)/(A-1); ans%=mod; } printf("%lld\n",ans); } int main() { Eprime(); scanf("%lld%lld",&A,&B); Devide(A,B); return 0; }
3.bzoj2486 http://www.lydsy.com/JudgeOnline/problem.php?id=2186
2186: [Sdoi2008]沙拉公主的困惑
Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 3994 Solved: 1386
[Submit][Status][Discuss]
Description
大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。
Input
第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n
Output
共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值
Sample Input
4 2
Sample Output
数据范围:
对于100%的数据,1 < = N , M < = 10000000
/* 题目大意:给定询问组数T和取模数P,每次询问给定两个整数n和m,求1~(n!)的数中与m!互质的数个个数模P (m<=n) 首先暴力肯定过不去,我们需要预处理一些东西 首先我们知道,若x与y互质,则x+y与y也互质,x+2y与y也互质。。。 换到这道题上来说,若一个数x与m!互质,那么x+(m!)也一定与m!互质,(x+m!*2)也一定与m!互质。。。 由于n!一定是m!的倍数,于是我们每存在到一个x<=m!与m!互质,我们就一定能找到(n!)/(m!)个与m!互质的数 而m!以内与m!互质的数的数量恰好是φ(m!) 于是我们要得到的数恰好就是φ(m!)*(n!)/(m!) %p 其中m!的所有质因数恰好就是m以内所有的质数 于是φ(m!)=(m!)*∏(pi-1)/pi (pi<=m) 于是最后我们的结果就是n!*∏(pi-1)/pi 质数预处理出来,n!预处理出来,pi的逆元预处理出来,∏(pi-1)/pi就可以预处理出来,都是线性的 bzoj 上用读入优化卡过了 洛谷比较强。。。 */ #include<bitset> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define M 10000001 #define ll long long using namespace std; bool not_prime[M+100]; ll prime[500500],ans[M+100],fac[M+100],rev[M+100]; int n,m,p,T,tot; void Linear_Shaker() { ll i,j; for(i=2;i<=M;i++) { if(!not_prime[i]) prime[++tot]=i; for(j=1;j<=tot&&prime[j]*i<=M;j++) { not_prime[prime[j]*i]=1; if(i%prime[j]==0) break; } } fac[1]=1; for(i=2;i<=M;i++) fac[i]=fac[i-1]*i%p; rev[1]=1; for(i=2;i<=M&&i<p;i++) rev[i]=(p-p/i)*rev[p%i]%p; ans[1]=1; for(i=2;i<=M;i++) { if(!not_prime[i]) ans[i]=ans[i-1]*(i-1)%p*rev[i%p]%p; else ans[i]=ans[i-1]; } } inline int init() { int x=0,f=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x*f; } int main() { T=init();p=init(); Linear_Shaker(); for(int i=1;i<=T;++i) { n=init();m=init(); printf("%d\n",fac[n]*ans[m]%p); } }
瞬间移动
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1422 Accepted Submission(s):
684
两个整数n,m(2≤n,m≤100000)
#include<iostream> #include<cstdio> #include<cstring> #define N 200001 #define M 1000000007 #define ll long long using namespace std; ll fac[N]={1,1},inv[N]={1,1},f[N]={1,1}; int n,m; ll C(ll a,ll b) { return fac[a]*inv[b]%M*inv[a-b]%M; } int main() { for(int i=2;i<N;i++) { fac[i]=fac[i-1]*i%M; f[i]=(M-M/i)*f[M%i]%M; inv[i]=inv[i-1]*f[i]%M; } while(~scanf("%d%d",&n,&m)) printf("%lld\n",C(m+n-4,m-2)); return 0; }