题意:
给定 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;
}