HDU3944 (Lucas定理+阶乘逆元预处理)

博客围绕杨辉三角中从顶点(0,0)走到给定点(n,m)的最小和问题展开。分n>=2m和n<2m两种情况给出答案公式,之后因有多组样例且n、m很大,需用Lucas定理对组合数进行预处理,包括对素数打表及对阶乘和阶乘逆元预处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:在一个杨辉三角中,从顶点(0,0)走到给定点(n,m)的最小和题意:在一个杨辉三角中,从顶点(0,0)走到给定点(n,m)的最小和(0,0)(n,m)
如图:
在这里插入图片描述
①对于n大于等于&gt;=2m时,如果走到(4,1),即C(mn)=4这个点,那么我们可以选择先一直往下走(取尽量多的1),然后走到与4成斜线时,往斜的方向走,因为越往下走值越大,直线走不了的时候走斜线一定是最优的。①对于n大于等于&gt;=2m时,如果走到(4,1),即C\tbinom{m}{n}=4这个点,那么我们可以选择先一直往下走(取尽量多的1),然后走到与4成斜线时,往斜的方向走,因为越往下走值越大,直线走不了的时候走斜线一定是最优的。n>=2m(4,1),C(nm)=414线线线

此时答案就是n−m+C(mn+1),就是取1的个数+斜线上的和此时答案就是 n-m+C\tbinom{m}{n+1},就是取1的个数+斜线上的和nm+C(n+1m),1+线

②对于n小于2m的情况,同理取尽量多的1,那么肯定先走斜线,走到与目标值在同一竖线上时,直接往下走直线,其实和第一种情况是一样的。②对于n小于2 m的情况,同理取尽量多的1,那么肯定先走斜线,走到与目标值在同一竖线上时,直接往下走直线,其实和第一种情况是一样的。n2m1线线线

此时答案就是m+C(n−mn+1),取1的个数+竖线上的和此时答案就是m+C\tbinom{n-m}{n+1},取1的个数+竖线上的和m+C(n+1nm)1+线

总结就是

ans=(n−m+C(mn+1))mod&ThinSpace;&ThinSpace;p,n&gt;=2mans = (n-m+C\tbinom{m}{n+1})\mod p,n&gt;=2mans=(nm+C(n+1m))modpn>=2m
ans=(m+C(n−mn+1))mod&ThinSpace;&ThinSpace;p,n&lt;2mans = (m+C\tbinom{n-m}{n+1})\mod p,n&lt;2mans=(m+C(n+1nm))modpn<2m

接下去就是如何处理这个组合数
由于有多组样例,所以需要进行预处理。并且n,m很大,普通的阶乘逆元打表肯定不行了。这时候就要用Lucas定理由于有多组样例,所以需要进行预处理。并且n,m很大,普通的阶乘逆元打表肯定不行了。这时候就要用Lucas定理n,mLucas

fac[j][i]表示在模第i个素数的情况下,j这个数的阶乘fac[j][i]表示在模第i个素数的情况下,j这个数的阶乘fac[j][i]ij
inv[j][i]表示在模第i个素数的情况下,j这个数的阶乘逆元inv[j][i]表示在模第i个素数的情况下,j这个数的阶乘逆元inv[j][i]ij
因为保证输入的模数是素数,所以先对1 10000的素数打表(有tot=1229个),然后对fac,inv预处理,最后套个Lucas因为保证输入的模数是素数,所以先对1~10000的素数打表(有tot=1229个),然后对fac,inv预处理,最后套个Lucas1 10000tot=1229fac,invLucas

#include<cstdio>
#include<iostream>
#define maxn 10010
#define ll long long
using namespace std;
int inv[maxn][1500],pri[maxn],num[1500],fac[maxn][1500],prid[maxn];
int n,m,p;
int tot = 0;
int id;
void getprime(){
    pri[1] = 1;
    for(int i = 2; i <= 10000; i++){
        if(pri[i] == 0){
            prid[i] = tot;
            num[tot++] = i;
            for(int j = i+i; j <= 10000; j+=i)
                pri[j] = 1;
        }
    }
}
int quickpow(int A,int B,int mod){
    A%=mod;
    int ans=1;
    while(B)
    {
        if(B&1)
        ans=(ans*A)%mod;
        A=(A*A)%mod;
        B>>=1;
    }
    return ans%mod;
}
void solve(){
    getprime();
    for(int i = 0; i < tot; i++){
        fac[0][i] = inv[0][i] = 1;
        for(int j = 1; j < num[i]; j++){
            fac[j][i] = (fac[j-1][i]*j)%num[i];
            inv[j][i] = quickpow(fac[j][i],num[i]-2,num[i]);
        }
    }
}
int CC(int n,int m){
    /*注意判断*/if(m>n) return 0;
    //注意取模 
    //费马小定理求逆元 
    return ((fac[n][id]*inv[m][id]%p)*inv[n-m][id]%p)%p;
}
int Lucas(int n,int m){
    if(!m) return 1;
    return CC(n%p,m%p)*Lucas(n/p,m/p)%p;//注意取模 
}
int main(){
    solve();
    int ca = 1;
    while(~scanf("%d%d%d",&n,&m,&p)){
        int ans;
        id = prid[p];
        if(n < 2*m) ans = m+Lucas(n+1,n-m);
        else ans = n-m+Lucas(n+1,m);
        printf("Case #%d: %d\n",ca++,ans%p);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值