题目
给定n,m,k(0<=n<=1e15,0<=m<=1e12,1<=k<=18),
求长度为k的数组a,ai为[0,m]的整数,
满足的方案数
答案对1e9+7取模
题解
第一反应想起了hdu3693,但比对了一下,感觉那个题难很多,
两年前写的题忘了写题解,现在不会了
Educational Codeforces Round 104 (Rated for Div. 2) F.Ones(数位dp)_Code92007的博客-CSDN博客
后来想了想,感觉更像这个题,按余数统计方案数,
控制余数在一定范围,姑且称它为数位背包吧
每一位的贡献可以独立考虑,而这个式子实际是(i,j)对的01值不同就会产生贡献,
所以只用关注当前几个填1几个填0,由于有的卡上界有的不卡,所以记录一下当前有几个卡上界
dp[i][j][l]表示当前从高到低考虑到低i位时,只考虑n的i位及更高位时的数的当前余数是j,选的l个数卡上界的方案数
k=18时,最大贡献是9*9=81,考虑当前n的余数x最大能是多少,
(x-81)*2<x,表示本位取了81之后,下一位的余数小于81,解得x<162,
所以转移过程中可以忽略>162的转移,因为通过更低的位,再也不能使其归0
而<0的转移也不可取,
因为这个操作,转移到下一位,相当于*2,下一位n有余数,相当于+1,
还有减去当前选的值,相当于减去一个非负数,而(-1)*2+1还为-1,最终不能变为0
所以,可以只关注[0,162)的转移,代码中写的是[0,256)
此外,初始局面时,n的余数超过256也显然无解
转移分两种情况讨论,当前位为0 或者 当前位为1
1. 当前位为0,卡上界的只能选0,for枚举不卡上界的数中选了x个1
2. 当前位为1,for枚举卡上界的数中选了x个1,for枚举不卡上界的数中选了y个1
选出这些数来,从而确定了贡献数对的数量,也确定了下一次卡上界的数的个数,
并算出下一位的n的当前余数,递归到子状态搜索即可
复杂度O(40*256*18*18*18),但跑不满
注意特判k=1(取不出两个数)和m=0(a数组为空)的情况
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=42,M=256,K=19,mod=1e9+7;
ll n,m,dp[N][M][K],c[K][K];
int k,a[N];
ll dfs(int x,int r,int cur){
if(r>=M)return 0;
if(x==-1)return r==0;
if(~dp[x][r][cur])return dp[x][r][cur];
ll &ans=dp[x][r][cur];ans=0;
int nb=(x==0?0:(n>>(x-1)&1));
//printf("x:%d ax:%d\n",x,a[x]);
if(a[x]==0){
for(int i=0;i<=k-cur;++i){
int nr=r-i*(k-i);
if(nr<0)continue;
ans=(ans+1ll*c[k-cur][i]*dfs(x-1,(nr<<1)|nb,cur)%mod)%mod;
}
}
else{
for(int i=0;i<=cur;++i){
for(int j=0;j<=k-cur;++j){
int nr=r-(i+j)*(k-(i+j));
//printf("x:%d r:%d cur:%d i:%d j:%d nr:%d\n",x,r,cur,i,j,nr);
if(nr<0)continue;
ans=(ans+1ll*c[cur][i]*c[k-cur][j]%mod*dfs(x-1,(nr<<1)|nb,i)%mod)%mod;
}
}
}
//printf("x:%d r:%d cur:%d ans:%lld\n",x,r,cur,ans);
return ans;
}
ll cal(){
if(k==1)return !n;
if(!m)return !n;
memset(dp,-1,sizeof dp);
int c=0;
for(ll x=m;x;x>>=1){
a[c++]=x%2;
}
ll rn=0;
for(int i=c;i<=50;++i){
if(n>>i&1){
rn+=1ll<<(i-c);
if(rn>=M)return 0;
}
}
return dfs(c,(int)rn,k);
}
int main(){
c[0][0]=1;
for(int i=1;i<K;++i){
c[i][0]=c[i][i]=1;
for(int j=1;j<i;++j){
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
cin>>n>>m>>k;
cout<<cal()<<endl;
return 0;
}