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/p∗Cmmodpnmodp(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;
}

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

被折叠的 条评论
为什么被折叠?



