bzoj 4737: 组合数问题

Description

 组合数C(n,m)表示的是从n个物品中选出m个物品的方案数。举个例子,从(1,2,3)三个物品中选择两个物品可以有(

1,2),(1,3),(2,3)这三种选择方法。根据组合数的定义,我们可以给出计算组合数C(n,m)的一般公式:
C(n,m)=n!/m!*(n?m)!
其中n!=1×2×?×n。(额外的,当n=0时,n!=1)
小葱想知道如果给定n,m和k,对于所有的0≤i≤n,0≤j≤min(i,m)有多少对(i,j)满足C(i,j)是k的倍数。

Input

第一行有两个整数t,k,其中t代表该测试点总共有多少组测试数据,k的意义见。
接下来t行每行两个整数n,m,其中n,m的意义见。

Output

 t行,每行一个整数代表所有的0≤i≤n,0≤j≤min(i,m)中有多少对(i,j))满足C(i,j)是k的倍数

答案对10^9+7取模。

由lucas定理的推论,C(i,j)不是k的倍数当且仅当k进制下i的每一位分别>=j

先补上前导零使n,m位数相同从高位到低位进行数位dp,f[i][0/1][0/1]表示当前已决策高于第i位的部分,已决策部分是否与n,m相等,满足条件的方案数。

#include<cstdio>
#include<cstring>
const int P=1000000007;
typedef long long i64;
int T,k,ns[107],ms[107],f[107][2][2];
i64 n,m;
int min(int a,int b){return a<b?a:b;}
int max(int a,int b){return a>b?a:b;}
i64 S(i64 x){return x%=P,x*(x+1)/2%P;}
i64 cal(i64 a,i64 b){
    if(a<b)b=a;
    return (S(a)-S(a-b))%P;
}
i64 s[107][107];
int main(){
    scanf("%d%d",&T,&k);
    for(int i=0;i<=k;++i)
    for(int j=0;j<=k;++j)s[i][j]=cal(i,j);
    for(;T;--T){
        scanf("%lld%lld",&n,&m);
        if(n<m)m=n;
        int ans=cal(n+1,m+1);
        int np=0,mp=0;
        while(n)ns[++np]=n%k,n/=k;
        while(m)ms[++mp]=m%k,m/=k;
        while(mp<np)ms[++mp]=0;
        memset(f,0,sizeof(f));
        f[np][1][1]=1;
        for(int i=np;i;--i){
            int(*F)[2]=f[i],(*G)[2]=f[i-1];
            int vn=ns[i],vm=ms[i];
            G[1][1]=F[1][1]*(vn>=vm);
            G[1][0]=(F[1][0]*i64(vn+1)+F[1][1]*(i64)min(vn+1,vm))%P;
            G[0][1]=(F[0][1]*i64(k-vm)+F[1][1]*(i64)max(vn-vm,0))%P;
            G[0][0]=(F[0][0]*s[k][k]+F[0][1]*s[k][vm]+F[1][0]*s[vn][k]+F[1][1]*s[vn][vm])%P;
        }
        for(int a=0;a<2;++a)
        for(int b=0;b<2;++b)
        ans=(ans-f[0][a][b])%P;
        printf("%d
",(ans+P)%P);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/ccz181078/p/6767097.html