HDU 3944 DP?(lucas定理 组合数)

坑点:

注意起点和终点也算。
这里写图片描述
(盗图自http://www.cnblogs.com/simplekinght/p/6986268.html?utm_source=itdadao&utm_medium=referral
解释一下这个样例2:
根据图中我们的标记,我们得知两种走法,(从终点到起点)先斜上,再上。或者先上,再斜上。
而选择这其中的哪一个呢?依据m的位置而定,如果m>=n/2 那么自然是先上。
下面我们考虑 m<=n/2 的情况。
ans(4,2)=c(4,2)+c(3,1)+c(2,0)+42 这个式子很明显。
然后我们考虑推广形式 ans(i,j)
ans(i,j)=c(i,j)+c(i1,j1)+c(i2,j2)...c(ij+1,1)+c(ij,0)+ij
这个要是算的话,复杂度还是蛮高的,跟m相关,所以试着化简。
根据递推式: cji=cji1+cj1i1 所以,我们把 c(ij,0) 换成 c(ij+1,0) ,我们知道这两个都等于1,可以换,然后我们就可以化简了。

ans(i,j)=c(i,j)+c(i1,j1)+c(i2,j2)...c(ij+1,1)+c(ij,1)+ij

=c(i,j)+c(i1,j1)+c(i2,j2)...c(ij+2,2)c(ij+2,1)+ij

=c(i+1,j)+ij

注意:

lucas定理在使用的过程中求c(i,j)的工程中需要提前预处理出阶乘取模的答案,然后用的时候直接算阶乘取模的逆元。否则会t。
注意在使用的过程中,n大于等于m。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
#define inf 0x3f3f3f3f
typedef long long int lli;
using namespace std;
int mod;
const int maxn = 1e4+20;
bool isprime[maxn];int prime[maxn];
void moblus(){
    int cnt = 0;
    for(lli i = 2;i < maxn;i++){
        if(!isprime[i]) prime[cnt++] = i;
        for(lli j = 0;cnt&&i*prime[j] < maxn;j++){
            int x = prime[j];
            isprime[i*x] = 1;
            if(i%x == 0) break;
        }
    }
}
int fac[10010][10010];
int inv[10010][10010];
inline lli qp(lli a,lli x){
    lli ans = 1;a%=mod;
    for(;x;x>>=1){
        if(x&1) ans = ans*a%mod;
        a = a*a%mod;
    }
    return ans%mod;
}
lli c(lli n,lli m){
    if(m>n) return 0;
    lli ans = 1;
    /*
    for(int i=1;i<=m;i++){//用逆元递推的求c(n,m) mod是素数 且a与mod 互质 a^(p-1) = 1
        lli a = (n+i-m)%mod;
        lli b = i%mod;
        ans = ans*(a*qp(b,mod-2)%mod)%mod;
    }
    */
    ans = (lli)fac[mod][n] * qp((lli)fac[mod][n-m]*(lli)fac[mod][m],mod-2)%mod;//预处理的p对x!的取模
    return ans;
}

lli lucas(lli n,lli m){
    if(m==0) return 1;
    return c(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}

int main(){
    lli t,n,m;int ncase = 0;
    moblus();
    for(int i = 2;i < 10010;i++){
        if(isprime[i]==0){
            fac[i][0]=1;
            for(lli j = 1;j < 10010;j++){
                fac[i][j] = (lli)fac[i][j-1]*j%i;
                if(fac[i][j]==0) break;
            }
        }
    }
    while(~scanf("%lld%lld%d",&n,&m,&mod)){
        ncase++;
        if(m>=n/2) m=n-m;
        printf("Case #%d: %lld\n",ncase,((lucas(n+1,m)+n-m)%mod+mod)%mod);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值