题意:
有n个数,每个数x 满足0<=x<=L,如果一列数中选出某些数和为k那么称这个数列为好的数列,
求能构造出这样的数列的个数。n和k最大为20,L最大为10^9。
输出结果要对1000000000+7取模。
算法:
由于k只有20,可以对能凑成的数用二进制整数的一个位来表示,k位置为1表示当前状态能得到
整数k,否则不能。dp[i][s]表示前i个数能得到状态s。
dp[i+1][s|((s<<j)&full)] = sum(dp[i][s] ) ,初始化为dp[1] = 1。
为什么新状态new = s|((s<<j)&full)呢? 比如:前i个数我可以得到sum=1,2,3,如果第i+1个数我取
2,则我能得到1,2,3相应的加2即得到3,4,5。也就是把s<<j。至于&full(全状态)是因为可能移位后
超过我要得到的全状态,记录dp[s]时,s'偏大了,相当于丢失了一部分记录所以结果偏小。
如果初始化为dp[0]=1,那么状态转移则为new = s|(1<<(j-1))|((s<<j)&full).
dp[i+1][new] = sum(dp[i][s])
为什么多了个|(1<<(j-1))呢?因为从dp[0]=1递推到每个sum等于所取的数本身就要从某一位置一开始。
#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 22
using namespace std;
typedef long long ll;
const int mod = 1000000000+7;
int dp[1<<maxn],n,l,k;
void solve()
{
int lim = min(l,k);
int full = (1<<k)-1;
memset(dp,0,sizeof(dp));
dp[0] = 1;
while(n--)
{
for(int s=full;s>=0;s--)
{
if(dp[s]>0)
{
ll tmp = dp[s];
for(int j=1;j<=lim;j++)
{
int next = s|(1<<(j-1))|((s<<j)&full);
dp[next] = (dp[next]+tmp)%mod;
}
if(lim<l)
dp[s] = (dp[s]+(tmp*(l-lim))%mod)%mod;
}
}
}
ll ans = 0;
for(int s=0;s<=full;s++)
{
if(s&(1<<(k-1)))
ans= (ans+dp[s])%mod;
}
printf("%I64d\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&k,&l);
solve();
}
return 0;
}
#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn (1<<22)
using namespace std;
typedef long long ll;
const int mod = 1000000000+7;
int dp[maxn],k,l,n;
void solve()
{
memset(dp,0,sizeof(dp));
int full = (1<<(k+1))-1;
int lim = min(l,k);
dp[1] = 1;
for(int i=0;i<n;i++)
{
for(int s=full;s>=0;s--)
{
if(dp[s]>0)
{
ll tmp = dp[s];
for(int j=1;j<=lim;j++)
dp[full&(s|(s<<j))] = (dp[full&(s|(s<<j))]+tmp)%mod;
if(lim<l)
dp[s] = (dp[s]+((l-lim)*tmp)%mod)%mod;
}
}
}
ll ans = 0;
for(int s=1;s<=full;s++)
{
if(s&(1<<k))
ans = (ans+dp[s])%mod;
}
printf("%I64d\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&k,&l);
solve();
}
return 0;
}