hdu 4906 Our happy ending 状态压缩dp

Our happy ending

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 752    Accepted Submission(s): 241


Problem Description
There is an old country and the king fell in love with a devil. The devil always asks the king to do some crazy things. Although the king used to be wise and beloved by his people. Now he is just like a boy in love and can’t refuse any request from the devil. Also, this devil is looking like a very cute Loli.

Y*wan still remember the day he first meets the devil. Now everything is done and the devil is gone. Y*wan feel very sad and suicide.

You feel guilty after killing so many loli, so you suicide too.

Nobody survive in this silly story, but there is still some hope, because this is just a silly background story during one programming contest!

And the last problem is:

Given a sequence a_1,a_2,...,a_n, if we can take some of them(each a_i can only be used once), and they sum to k, then we say this sequence is a good sequence.

How many good sequence are there? Given that each a_i is an integer and 0<= a_i <= L.

You should output the result modulo 10^9+7.
 

Input
The first line contains an integer T, denoting the number of the test cases.
For each test case, the first line contains 3 integers n, k, L.

T<=20, n,k<=20 , 0<=L<=10^9.
 

Output
For each cases, output the answer in a single line.
 

Sample Input
   
   
   
   
1 2 2 2
 

Sample Output
   
   
   
   
6
 
题意:给你n个数,0到l的范围,问有几种组合可以使得从这n个数中,选择一些数相加的和是k


解题思路:  假设给了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;
}


你可能感兴趣的:(end,happy,状态压缩,2014多校联合训练赛,hdu4906Our)