1 2 2 2
6
解题思路: 假设给了x个数字,定义该集合为X,这些数字任意相加可以得到一些和的集合,定义为sum,
在X中添加一个数字y,那么这个数字与原来X中的数字组合可以得到新的和的集合,
而这些和包括 y,sum,sum中任意一个数字加上y (sum' ),得到的就是一个新的sum集合
可以知道只要sum中包含了k,那么就是一个可行的集合
定义状态: 状态S 表示为sum的集合,如果sum中有i这个元素,那么S的第i位就是1
S第i位为1表示这个状态可以选择一些数字相加得到i
那么一个新的S' = (1<<(i-1)) | S | (S<<i) 即y,sum,sum‘
S<<i这个想法很好,我做的dp比较少,刚开始没想通,多了一维去计算sum’集合了,复杂度多了一个20,打表的时候就用了300s
转移方程: 对于状态S,S添加一个数i,可以转移到一个指定的新状态S',转移方式如上
对于有n个数字的情况,那么需要n次转移,因为每次可以添加一个新的数字
对于状态S我们只关系 S <= (1<<k-1)的情况,大于的情况不需要记录,因为大于k的数,加上任意数字(非负)还是大于k
所以对S'&((1<<k)-1)即可
对于S‘如果可以得到k,那么就是所要求的状态了,直接加到状态 1<<(k-1)即可,
其他分析:
在比赛的时候不敢做这题,因为复杂度是n^3*2^k的,其中一个n是因为我没想过sum<<i这个方式
明显会超时的,但是解题报告就是n^2*2*k的复杂度,不得不说太不可思议了,居然不超时
虽然打表也是可以过的,而且比较快。 但是直接求的话可以不管k和l的大小关系
但是数据没有l比k小的情况,因为cs打了一个20*20的表格(我的表格打错了,wa了),表示n个格子,最大值为k且可以得到k的时候的组合数
显然,有了这个表格,只要再求一下组合数即可,预处理C(i,n)就可以在n的复杂度计算得到结果了。
但是这个存在问题就是l比k小,表格的数据就不完全了(实际数据不存在这种情况),因此其实要得到的是三维表格n*k*k表示n个格子,最大值为k'(k'<k),可以得到和为k的组合数
代码的code length超大滴,
#include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<vector> using namespace std; #define maxn 1<<21 #define mod 1000000007 #define ll long long ll dp[2][maxn]; void add(ll&a,ll b){ a+=b; if(a >= mod) a-=mod; } int main(){ int t,n,l,k,i,j; scanf("%d",&t); while(t--){ scanf("%d%d%d",&n,&k,&l); int p=0,q=1,f=min(l,k),s=1<<(k-1),s1,s2,s3=(1<<k)-1; memset(dp[0],0,sizeof(dp[0])); dp[0][0] = 1; for( i = 0;i < n; i++){ if(k>16)memset(dp[q],0,sizeof(dp[q])); else for(j=0;j<=s;j++)dp[q][j]=0; for(j = 0;j <= s;j++){ if(dp[p][j] == 0) continue; dp[q][j] = (dp[q][j]+dp[p][j]*(l-f))%mod; add(dp[q][j],dp[p][j]); for(s1=1;s1<=f;s1++){ s2 = (1<<(s1-1))|j|(j<<s1); if(s2&s)add(dp[q][s],dp[p][j]); else add(dp[q][s2&s3],dp[p][j]); } } swap(p,q); } cout<<dp[p][s]<<endl; } return 0; }