【BZOJ】2186 沙拉公主的困惑

来源:互联网 发布:雀森莲x先导爱知漫画 编辑:程序博客网 时间:2024/04/29 08:52
一道非常有价值的题。

【解析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];   //screenint pre[N]; //Prefix n!int cas,r,n,m;  //Basicint 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];  //screenint inv[N],sinv[N]; //Mutiplicative Inverseint 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时有用。

0 0
原创粉丝点击