bzoj 4737: 组合数问题

本文介绍了一种利用卢卡斯定理的推论解决特定组合数问题的方法,并通过数位DP实现。问题要求计算在给定n、m和k的情况下,有多少对(i, j)满足0≤i≤n且0≤j≤min(i,m),使得C(i,j)是k的倍数。

题意

组合数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的倍数。

题解

这题的话,要用的一个卢卡斯定理的推论

有非负整数A、B,和素数p,A、B写成p进制为:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]。则组合数C(A,B)与C(a[n],b[n])×C(a[n-1],b[n-1])×...×C(a[0],b[0]) mod p同余。

证明并没有看QAQ
但是知道这个以后就可以数位DP了
显然的,因为要mod k=0,那么里面的组合数至少有一个是0
那么就可以了

具体DP状态在我代码里面的注释有写
CODE:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const LL N=70;
const LL MOD=1e9+7;
LL T,k;
LL n,m;
LL a[N],b[N];
LL cnt;
LL f[N][2][2][2][2];
LL dfs (LL x,bool tf,bool tf1,bool tf2,bool tf3)//现在构到第几位    两个数是否一直都是一样的    是否出现了第一个a比b小的    第一个是否还有限制   第二个是否还有限制
{
    if (f[x][tf][tf1][tf2][tf3]!=-1) return f[x][tf][tf1][tf2][tf3];
    if (x<=0)   return tf1;

    LL lalal=0;
    if (tf==true)//如果两个一直都是一样的,那么后面的数就不可以比前面的数大 
    {
        if (tf2==true)//如果第一个还有限制 
        {
            if (tf3==true)//如果第二个还有限制
            {
                for (LL u=0;u<=a[x];u++)//第一个数填什么
                {
                    for (LL i=0;i<min(b[x],u);i++)
                    {
                        lalal=lalal+dfs(x-1,false,false,(u==a[x]),false);
                        lalal%=MOD;
                    }
                    if (u<b[x])//还是填一样的
                        lalal=lalal+dfs(x-1,true,false,(u==a[x]),false);
                    if (u==b[x])//刚好填这个
                        lalal=lalal+dfs(x-1,true,false,(u==a[x]),true);
                    if (u>b[x])
                        lalal=lalal+dfs(x-1,false,false,(u==a[x]),true); 
                    lalal%=MOD;
                }
            }
            else//第二个随便填 
            {
                for (LL u=0;u<=a[x];u++)//第一个数填什么 
                    for (LL i=0;i<=u;i++)
                    {
                        lalal=lalal+dfs(x-1,(i==u),false,(u==a[x]),tf3);
                        lalal%=MOD;
                    }
            }
        }
        else
        {
            if (tf3==true)//第二个有限制,第一个没有
            {
                for (LL u=0;u<k;u++)//第一个数填什么
                {
                    for (LL i=0;i<min(b[x],u);i++)
                    {
                        lalal=lalal+dfs(x-1,false,false,false,false);
                        lalal%=MOD;
                    }
                    if (u<b[x])//还是填一样的
                        lalal=lalal+dfs(x-1,true,false,false,false);
                    if (u==b[x])//刚好填这个
                        lalal=lalal+dfs(x-1,true,false,false,true);
                    if (u>b[x])
                        lalal=lalal+dfs(x-1,false,false,false,true); 
                    lalal%=MOD;
                }
            }
            else//两个都可以随便填了 
            {
                for (LL u=0;u<k;u++)
                    for (LL i=0;i<=u;i++)
                    {
                        lalal+=dfs(x-1,(u==i),false,false,false);
                        lalal%=MOD;
                    }
            }
        }
    }
    else//两个数不是一直一样的 
    {
        LL lim,lim1;
        if (tf2==true) lim=a[x];
        else lim=k-1;
        if (tf3==true) lim1=b[x];
        else lim1=k-1;
        for (LL u=0;u<=lim;u++)
            for (LL i=0;i<=lim1;i++)
            {
                lalal=lalal+dfs(x-1,false,tf1|(u<i),tf2&(u==lim),tf3&(i==lim1));
                lalal%=MOD;
            }
    }
    f[x][tf][tf1][tf2][tf3]=lalal;
    /*printf("%lld %lld %lld %lld %lld %lld\n",x,tf,tf1,tf2,tf3,lalal);
    system("pause");*/
    return lalal;
}
int main()
{
    scanf("%lld%lld",&T,&k);
    while (T--)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        memset(f,-1,sizeof(f));
        scanf("%lld%lld",&n,&m);
        LL h=0;
        while (n>0) {a[++h]=n%k;n/=k;}
        cnt=h;h=0;
        while (m>0) {b[++h]=m%k;m/=k;}
        cnt=max(cnt,h);
    /*  for (LL u=cnt;u>=1;u--) printf("%lld ",a[u]);
        printf("\n");
        for (LL u=cnt;u>=1;u--) printf("%lld ",b[u]);
        printf("\n");*/
        printf("%lld\n",dfs(cnt,true,false,true,true));
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值