lucas定理+费马小定理方法求逆元-HDU3944

来源:互联网 发布:网络歌手袁晓婕诈骗 编辑:程序博客网 时间:2024/06/05 12:03

https://vj.xtuacm.cf/contest/view.action?cid=59#problem/E
这题目用的线性方法求逆元要记住

题目的意思是说,在杨辉三角上找一条路径,每次必须往下走一行,使得这个路径上所有数字的总和最小,输出取模值。

这里对于组合数有一个变换。C(n-1,k-1)+C(n-1,k)=C(n,k)

同时,仔细观察杨辉三角就知道,对于每行,从左至中间都是递增的,同时又根据对称性,把所有大于每行中点的点对称到左边来。

这样从目标点出发,每次都往左上方走,然后,走到第0列的时候就往上一直走(全为1),即最小值。

这样其结果可以表示为:

C(n,k)+C(n-1,k-1)+……+C(n-k+1,1)+C(n-k,0)+C(n-k-1,0)+……C(0,0);

注意观察这个式子和我开始列出来的那个公式,如果我们把C(n-k,0)写成C(n-k+1,0)(为什么?因为都是1呗!),

这样,上面式子昨天那一段,可以从尾到头依次合并,最终合并为C(n+1,k),ORZ。所以这里只要求一次组合数就可以了呢。

但是问题又来了,题目明确说了有100000组数据,而一般用Lucas定理求组合数的时间复杂度和P同级。这里P<=10000。所以这样做就会超时呢。

定义一个数组,所有小于10000的数的阶乘对所有小于10000的的素数进行取模,这样到lucas定理计算的时候就可以直接用。同时对逆元进行打表处理。

#include <iostream>#include <cstdio>#include <cmath>using namespace std;int mod;int prime[1500],shu[10005],cnt=0;//质数打表int fac[10005][10005],inv[10005][10005];//阶乘打表,逆元打表void init1()//质数打表{    for(int i=2;i<=10000;i++)    {        if(!shu[i])        {            prime[cnt++]=i;            for(int j=i*2;j<=10000;j+=i)                shu[j]=1;        }    }}int quick_pow(int n,int m,int k){    int ans=1;    while(m)    {        if(m&1)ans=(ans*n)%k;        m>>=1;        n=(n*n)%k;    }    return ans;}void init2()//预处理阶乘表和逆元表{    for(int i=0;i<cnt;i++)    {        fac[prime[i]][0]=inv[prime[i]][0]=1;        for(int j=1;j<=prime[i];j++)        {            fac[prime[i]][j]=(fac[prime[i]][j-1]*j)%prime[i];            inv[prime[i]][j]=quick_pow(fac[prime[i]][j],prime[i]-2,prime[i]);          //费马小定理求逆元        }    }}int C(int m,int n)//组合数{    if(n>m)return 0;    return fac[mod][m]*(inv[mod][n]*inv[mod][m-n]%mod)%mod;}int lucas(int m,int n)//lucas定理{    if(!n)return 1;    else        return C(m%mod,n%mod)*lucas(m/mod,n/mod);}int main(){    int k,n,p;    int ca=1;    init1();    init2();    while(~scanf("%d%d%d",&n,&k,&p))    {        mod=p;        if(k>n/2)            k=n-k;        int s=(lucas(n+1,k)+n-k)%mod;        printf("Case #%d: %d\n",ca++,s);    }    return 0;}