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

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值