题意:
给定 n 和 k。计算有多少长度为 k 的数组 a1, a2, ..., ak,(0≤ai) 满足:a1 + a2 + ... + ak = n。
对于任意的 i = 0, ..., k - 1 有 ai AND ai + 1 = ai + 1。其中AND是与操作.
题解:
分析ai&ai+1=ai+1这个操作,我们会发现,ai+1必须比ai小或者等于ai才能满足,并且将其化成二进制会发现:
例如 ai+1=10 那么ai可以使:10 110 1110 11110....既然得到这个规律那么我们就可以根据数位dp按照位进行阶段划分,然后求解。dp[i][j]表示到第i为位置,和为j的个数。那么枚举这两状态外,还要枚举下一位1的个数,根据规律发现要满足条件时,某位有1都是往前靠的,比如说现在位i=2 那么这个为的1的排列只能是这样:
1 1 1 1、1 1 1 0、1 1 0 0、1 0 0 0、0 0 0 0.那么枚举1的个数其实就直接得到排列了,那么只要加上这位对应的和就ok了,不用考虑1的排列问题。
状态:dp[i+1][j+k]+=dp[i][j];
总的复杂度是O(n*k*17),但是想到这里就直接放弃不写了,因为这复杂度太高了,又想不到好的方法解决。。事实上复杂度没这么高!因为有一个地方可以优化!那么就是枚举1的个数时,我们令sum表示目前位置的总和,那么只要sum>n就break,这样时间优化非常多,至少10倍,于是就可以过了!
#include<iostream> #include<math.h> #include<stdio.h> #include<algorithm> #include<string.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define B(x) (1<<(x)) typedef long long ll; void cmax(int& a,int b){ if(b>a)a=b; } void cmin(int& a,int b){ if(b<a)a=b; } const int oo=0x3f3f3f3f; const int MOD=1000000009; const int maxn=10005; ll dp[20][maxn]; int main(){ //freopen("E:\\read.txt","r",stdin); int k,n,T; scanf("%d",&T); while(T--){ scanf("%d %d",&k,&n); memset(dp,0,sizeof dp); dp[0][0]=1; for(int i=0;i<17;i++){ for(int j=0;j<=n;j++){ if(dp[i][j]==0)continue; for(int l=0;l<=k;l++){ ll sum=j+(l<<i); if(sum>n)break; dp[i+1][sum]=(dp[i+1][sum]+dp[i][j])%MOD; } } } cout<<dp[17][n]<<endl; } return 0; }