[bzoj4737][数论][DP]组合数问题

本文探讨了组合数的概念及计算方法,并引入了Lucas定理来解决特定的组合计数问题。通过数位DP的方式实现了对于大数值n、m情况下组合数C(n,m)是k的倍数的对数计数。

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取模。

Sample Input

3 23

23333333 23333333

233333333 233333333

2333333333 2333333333

Sample Output

851883128

959557926

680723120

HINT

1≤n,m≤10^18,1≤t,k≤100,且 k 是一个质数’

题解

个个都说很显然我辣鸡当然不觉得
考虑lucas定理

Cmn=Cm/pn/pCmmodpnmodp(modp)Cnm=Cn/pm/p∗Cnmodpmmodp(modp)

这里p就等于k了嘛..
其实就相当于把n,m写成p进制,设b1[u]表示n在p进制下的第u位,b2[u]表示m在p进制下的第u位
如果有至少一位i使得b1[i] < b2[i]
那么这个数mod p是为0的
于是就可以愉快数位dp了
实现怎么这么复杂啊..
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const LL mod=1e9+7;
int T,k;LL n,m,ans;
int s1[1100],s2[1100],ln1,ln2;
void get(LL u,int op)
{
    if(op==1)
    {
        ln1=0;
        while(u)s1[++ln1]=u%k,u/=k;
    }
    else
    {
        ln2=0;
        while(u)s2[++ln2]=u%k,u/=k;
    }
}
LL pow_mod(LL a,LL b)
{
    if(!a)return 0;
    LL ret=1;
    while(b)
    {
        if(b&1)ret=ret*a%mod;
        a=a*a%mod;b>>=1;
    }
    return ret;
}
void ad(LL &x,LL y){x+=y;if(x>mod)x-=mod;}
LL f[70][2][2][2][2];
LL dp(int p,int op1,int op2,int op3,int op4)//位置 是否卡上界 是否出现第一个m比n大的数   是否出现第一个m比n小的数 
{
    if(f[p][op1][op2][op3][op4]!=-1)return f[p][op1][op2][op3][op4];
    if(p<=0)return op4&op3;
    LL ret=0;
//  if(!op1&&!op2&&op3&&op4)return f[p][op1][op2][op3][op4]=pow_mod(k,p);
    if(!op4)//这一位不能大于 
    {
        if(op1)//m限制 
        {
            if(op2)//n限制 
            {
                for(int i=0;i<s1[p];i++)
                {
                    if(i<s2[p])ad(ret,dp(p-1,0,0,op3,0));
                    for(int j=i+1;j<s2[p];j++)ad(ret,dp(p-1,0,0,op3,1));
                    if(i<s2[p])ad(ret,dp(p-1,0,1,op3,1));
                    else if(i==s2[p])ad(ret,dp(p-1,0,1,op3,0));
                }
                if(s1[p]<s2[p])ad(ret,dp(p-1,1,0,op3,0));
                for(int j=s1[p]+1;j<s2[p];j++)ad(ret,dp(p-1,1,0,op3,1));
                if(s1[p]<s2[p])ad(ret,dp(p-1,1,1,op3,1));
                else if(s1[p]==s2[p])ad(ret,dp(p-1,1,1,op3,0));
                return f[p][op1][op2][op3][op4]=ret;
            }
            else//n不限制 
            {
                for(int i=0;i<s1[p];i++)
                {
                    if(i<k)ad(ret,dp(p-1,0,0,op3,0));
                    for(int j=i+1;j<k;j++)ad(ret,dp(p-1,0,0,op3,1));
                }
                if(s1[p]<k)ad(ret,dp(p-1,1,0,op3,0));
                for(int j=s1[p]+1;j<k;j++)ad(ret,dp(p-1,1,0,op3,1));
                return f[p][op1][op2][op3][op4]=ret;
            }
        }
        else//m不限制 
        {
            if(op2)//n限制 
            {
                for(int i=0;i<k;i++)
                {
                    if(i<s2[p])ad(ret,dp(p-1,0,0,op3,0));
                    for(int j=i+1;j<s2[p];j++)ad(ret,dp(p-1,0,0,op3,1));
                    if(i<s2[p])ad(ret,dp(p-1,0,1,op3,1));
                    else if(i==s2[p])ad(ret,dp(p-1,0,1,op3,0));
                }
                return f[p][op1][op2][op3][op4]=ret;
            }
            else//n不限制 
            {
                for(int i=0;i<k;i++)
                {
                    if(i<k)ad(ret,dp(p-1,0,0,op3,0));
                    for(int j=i+1;j<k;j++)ad(ret,dp(p-1,0,0,op3,1));
                }
                return f[p][op1][op2][op3][op4]=ret;
            }
        }
    }
    else
    {
        int l1,l2;
        if(op1)l1=s1[p];else l1=k-1;
        if(op2)l2=s2[p];else l2=k-1;
        for(int i=0;i<=l1;i++)
            for(int j=0;j<=l2;j++)
                ad(ret,dp(p-1,op1&(i==l1),op2&(j==l2),op3|(i>j),1));
        return f[p][op1][op2][op3][op4]=ret;
    }
}
int main()
{
    scanf("%d%d",&T,&k);
    while(T--)
    {
        scanf("%lld%lld",&n,&m);
        memset(f,-1,sizeof(f));
        if(m>n)m=n;
        get(m,1);get(n,2);
        for(int i=ln1+1;i<=ln2;i++)s1[i]=0;
        ln1=ln2;ans=0;
        printf("%lld\n",dp(ln1,1,1,0,0));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值