题目
给定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
所以转移过程中可以忽略>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
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>n>>m>>k;cout<