一道非常有价值的题。
【解析1】欧几里德算法求乘法逆元,前缀和
[Analysis]O(T n log n)。
[Sum]
①int运算,如果会超出界,第一个数前要加上(LL)即类型转换。
②gcd不变的欧几里德定理:可以是加,也可以是减。
[Code]
/************************************************************** Problem: 2186 User: y20070316 Language: C++ Result: Accepted Time:6496 ms Memory:157056 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; typedef long long LL; const int N=10000001; int p[N],v[N],inv[N]; //screen int pre[N]; //Prefix n! int cas,r,n,m; //Basic int x,y; //Exgcd void exgcd(int i,int j) { if (!j) {x=1,y=0;return;} exgcd(j,i%j); int x_=y,y_=x-(i/j)*y; x=x_,y=y_; } int main(void) { scanf("%d%d",&cas,&r); for (int i=2;i<N;i++) { if (!v[i]) { p[++p[0]]=i; exgcd(i,r),inv[i]=x%r; } for (int j=1;j<=p[0];j++) { if (i*p[j]>=N) break; v[i*p[j]]=1; if (i%p[j]==0) break; } } pre[0]=1; for (int i=1;i<N;i++) pre[i]=(LL)pre[i-1]*i%r; inv[1]=1; for (int i=2;i<N;i++) if (!inv[i]) inv[i]=inv[i-1]; else { inv[i]=(LL)inv[i]*(i-1)%r; inv[i]=(LL)inv[i]*inv[i-1]%r; } for (int cc=1;cc<=cas;cc++) { scanf("%d%d",&n,&m); printf("%d\n",((LL)pre[n]*inv[m]%r+r)%r); } return 0; }</span>
【解析2】递推求乘法逆元,前缀和
[Analysis]O(Tn)
性质:关于Mod M作用下i的逆元inv[i]=-(M/i)*inv[M%i]。
证明:
令a=M/i,b=M%i,
∴M=ai+b。
∴inv[i] = -a * inv[b]。
同余式两边同时乘上i,得:
i * inv[i]
= -ai * inv[b]
= (b-M) * inv[b]
= b*inv[b]
= 1 (mod M)
∴inv[i]为在Mod M下i的逆元,证毕。
O(n)求法比直接求所有素数的逆元还慢一些...
[Code]
/************************************************************** Problem: 2186 User: y20070316 Language: C++ Result: Accepted Time:7700 ms Memory:196116 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; typedef long long LL; const int N=10000001; int p[N],v[N]; //screen int inv[N],sinv[N]; //Mutiplicative Inverse int pre[N]; //Prefix n! int cas,r,n,m; //Basic int main(void) { scanf("%d%d",&cas,&r); for (int i=2;i<N;i++) { if (!v[i]) p[++p[0]]=i; for (int j=1;j<=p[0];j++) { if (i*p[j]>=N) break; v[i*p[j]]=1; if (i%p[j]==0) break; } } pre[0]=1; for (int i=1;i<N;i++) pre[i]=(LL)pre[i-1]*i%r; inv[1]=1; for (int i=2;i<N;i++) inv[i]=(LL)(r-r/i)*inv[r%i]%r; sinv[1]=1; for (int i=2;i<N;i++) { sinv[i]=sinv[i-1]; if (!v[i]) { sinv[i]=(LL)sinv[i]*(i-1)%r; sinv[i]=(LL)sinv[i]*inv[i]%r; } } for (int cc=1;cc<=cas;cc++) { scanf("%d%d",&n,&m); printf("%d\n",((LL)pre[n]*sinv[m]%r+r)%r); } return 0; }</span>
下面是做这道题时做的一些笔记:
1.阶乘的乘除
①直接计算。
②分解质因数。
如果有取余,用①方便。
如果要写高精度,用②方便。
2、欧拉函数的求法
例:求fai(60)
①分解质因数正规求法
60=2^2 * 3^1 * 5^1,
∴fai(60)=60 * (1-1/2) * (1-1/3) * (1-1/5) = 16。
②根据①的另一种求法
fai(60)= (1 * 2^1) * (2*3^0) * (4*5^0)= 16。
③积性函数的解法:可结合欧拉筛法达到O(n)求出所有。
fai(60) = fai(2^2) * fai(3^1) * fai(5^1) = 16。
3、乘法逆元(mutiplicative inverse)
(1)什么是乘法逆元?
群G中任意一个元素a,都在G中有唯一的逆元a‘,s.t. aa‘=a‘a=e,e为单位元。
例:求4关于1模7的逆元,即求关于X的方程 4X ≡1 (mod 7)。
(2)怎么求乘法逆元?
在求乘法逆元aa‘=b(mod c)前,要满足(a,c)=1即(a,c)互质。
①同余方程 --> 不定方程 --> exgcd。 单个,O(log n)。
②欧拉定理
根据欧拉定理,当a与P互质时,a ^ fai(P) = 1 (mod P)。
∴a * a^(fai(P)-1) =1 (mod P)。
在mod P意义下a的乘法逆元a‘ = a^(fai(P)-1)。
特别的,当P为质数时,a‘ = a^(P-2)。 单个,O(log fai(P)-1)。
③积性函数
乘法逆元是积性函数,可以线性筛(screen)。
对于素数考虑以上两种方法哪种好。 所有,O(log n)或者O(fai(P)-1),一般来说用①。
④递推法。 所有,O(n)。
关于递推法,见:
http://blog.csdn.net/whyorwhnt/article/details/19169035。
(3)一个经典的问题:求(a/b) mod p。
性质:设b‘为b的逆元,即:b‘b=1(mod p),那么 a/b = a*b‘ (mod p)。
证:
∵b‘b=1(mod p)
∴b‘b=1+px即b‘=(1+px)/b。
∴a*b‘=a*(1+px)/b=a*(1+0)/b(mod p)=a/b (mod p),证毕。
这个性质在a变求和边取模,然后求(a/b) mod p时有用。
版权声明:本文为博主原创文章,未经博主允许不得转载。