【解析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時有用。