HDU_3565 Are You Busy(DP,状态转换)

来源:互联网 发布:什么域名代表公司 编辑:程序博客网 时间:2024/06/10 18:39

AreYouBusy
Description
Happy New Term! As having become a junior, xiaoA recognizes that there is not much time for her to AC problems, because there are some other things for her to do, which makes her nearly mad.
What’s more, her boss tells her that for some sets of duties, she must choose at least one job to do, but for some sets of things, she can only choose at most one to do, which is meaningless to the boss. And for others, she can do of her will. We just define the things that she can choose as “jobs”. A job takes time , and gives xiaoA some points of happiness (which means that she is always willing to do the jobs).So can you choose the best sets of them to give her the maximum points of happiness and also to be a good junior(which means that she should follow the boss’s advice)?

Input
There are several test cases, each test case begins with two integers n and T (0<=n,T<=100) , n sets of jobs for you to choose and T minutes for her to do them. Follows are n sets of description, each of which starts with two integers m and s (0< m<=100), there are m jobs in this set , and the set type is s, (0 stands for the sets that should choose at least 1 job to do, 1 for the sets that should choose at most 1 , and 2 for the one you can choose freely).then m pairs of integers ci,gi follows (0<=ci,gi<=100), means the ith job cost ci minutes to finish and gi points of happiness can be gained by finishing it. One job can be done only once.

Output
One line for each test case contains the maximum points of happiness we can choose from all jobs .if she can’t finish what her boss want, just output -1 .

Sample Input
3 3
2 1
2 5
3 8
2 0
1 0
2 1
3 2
4 3
2 1
1 1

3 4
2 1
2 5
3 8
2 0
1 1
2 8
3 2
4 4
2 1
1 1

1 1
1 0
2 1

5 3
2 0
1 0
2 1
2 0
2 2
1 1
2 0
3 2
2 1
2 1
1 5
2 8
3 2
3 8
4 9
5 10

Sample Output
5
13
-1
-1

题解:
先把题意给梳理一下:

新学期开学,老师分配任务。一共有n组工作,要求在T时间内完成。每组工作都有m个不同工作选择,每个工作选择对应消耗时间与回报。并且每组工作对应不同的类型s:
0:至少选择一个;
1:最多选择一个;
2:选任意个;

题目很明显属于分组背包,但是不同分组内的状态转换需要仔细研究一下。参考了网上和同学的代码,这里梳理一下思路。先从二维dp慢慢理解。
定义dp[i][j]:完成到第i组,消耗时间j得到的最大回报。

(类型0可以看做是1和2的结合所以我们先讲1和2)
类型1:
状态1表示从m个中最多选一个。这种类型我们还是第一次见,但是仔细想一下,最多选一个,也就是说本组内工作不能重复选,也就是每个工作不能有重叠更新(下一个工作在上一个的基础上更新),这就要求1~m,m个工作每件工作都只能在上一组工作的基础上去更新,本组内不能互相影响,最终我们得到的就只是选择一个或不选的最优结果。为保证全局最优解,需要先复制上一组的结果。
递推式:dp[i][j]=max( dp[i][j],dp[i-1][j-c[k]]+w[k] );
类型2:
状态2表示从m个中选择任意个工作完成,那就是简单的01背包。本组内的更新结果可以互相影响。同样需要复制上一组的结果。
递推式:dp[i][j]=max( dp[i][j],dp[i][j-c[k]]+w[k] );
类型0:
状态0表示至少选择1个,那就说明我们在保证一定有一个选上的情况下还能有更多的被选上。是类型1和类型2的结合。本组内的每一个工作在受到上一组影响的同时,还可以与本组内的其他工作相互影响。不能复制上一组结果。但是如何才能保证一定能选上一个呢(注意方法1方法2都可以一个都不选)?一种方法是:对于dp[i]初始化很小-INF,这样一定会在与上一组的比较中更新(除非时间上不满足);另一种方法是初始化dp为-1,dp[0]为0,保证0组中被选中的工作不能被覆盖,因为其他初始值都为-1,一旦更新后不会再改变(证明:若0组为第1组,一定能更新;若0组为第i组(i!=1),则dp[i-1][0]一定等于0,所以第i组能更新。)。
递推式:dp[i][j]=max( dp[i][j], dp[i-1][j-c[k]]+w[k],dp[i][j-c[k]]+w[k] );

关系比较复杂,想要更好的理解还需要去继续对比,思考,debug。

我的思考:
这道题对于状态的转换要求有很深入的思考,之前在基地推公式的时候没有想过那么深。其实对于dp关键就在于状态的转换,我们在做dp题目的时候不仅要搞清楚状态之间如何转换,也需要考虑从哪些合理的状态去转换,考虑状态相互转换的影响,比如01背包,完全背包,他们状态转换的不从其实就在于我们后一个状态能从哪些状态去继续更新。状态转换是dp的核心,需要深入理解的。

下面是两种不同的代码:
方法一:

#include <iostream>#include <cstdio>#include <cstring>#define MAX 110using namespace std;int dp[MAX][MAX];int n,T,m,s;int c[MAX],w[MAX];int main(){    while( scanf("%d%d",&n,&T)!=EOF )    {        memset(dp,-1,sizeof(dp));        memset(dp[0],0,sizeof(dp[0]));        for( int i = 1; i <= n; i++ )        {            scanf("%d%d",&m,&s);            memset(c,0,sizeof(c));            memset(w,0,sizeof(w));            for( int j = 0; j < m; j++ )                scanf("%d%d",&c[j],&w[j]);            if( s == 0 )            {                for( int x = 0; x < m; x++ )                {                    for( int y = T; y >= c[x]; y-- )                    {                        //顺序不能变,因为能做多个工作尽量不作为第一个,保证最大值                        if( dp[i][y-c[x]] != -1 )                            dp[i][y]=max(dp[i][y],dp[i][y-c[x]]+w[x]);                        if( dp[i-1][y-c[x]] != -1 )                            dp[i][y]=max(dp[i][y],dp[i-1][y-c[x]]+w[x]);                    }                }            }            else if( s== 1 )            {                for( int x = 0; x <= T; x++ )                    dp[i][x]=dp[i-1][x];                for( int x = 0; x < m; x++ )                {                    for( int y = T; y >= c[x]; y-- )                    {                        if( dp[i-1][y-c[x]] != -1 )                            dp[i][y]=max(dp[i][y],dp[i-1][y-c[x]]+w[x]);                    }                }            }            else            {                for( int x = 0; x <= T; x++ )                    dp[i][x]=dp[i-1][x];                for( int x = 0; x < m; x++ )                {                    for( int y = T; y >= c[x]; y-- )                    {                        if( dp[i][y-c[x]] != -1 )                            dp[i][y]=max(dp[i][y],dp[i][y-c[x]]+w[x]);                    }                }            }        }        printf("%d\n",dp[n][T]);    }    return 0;}

方法二:

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;//dp[i][j]:前i组工作,时间为j的最大快乐值。int n, t, m, s, dp[105][105], cost[105], value[105];int i, j, k;int main() {    while (scanf("%d%d", &n, &t) != EOF) {        for (i = 1; i <= n; i ++) {            scanf("%d%d", &m, &s);            for (j = 0; j < m; j ++) {                scanf("%d%d", cost + j, value + j);            }            switch(s) {            case 0:                for (j = 0; j <= t; j ++) {                    dp[i][j] = -0x7fffff;                }                for (j = 0; j < m; j ++) {                    for (k = t; k >= cost[j]; k --) {                        dp[i][k] = max(dp[i][k],                                       max(dp[i-1][k-cost[j]] + value[j], dp[i][k-cost[j]] + value[j]));                    }                }                break;            case 1:                for (j = 0; j <= t; j ++) {                    dp[i][j] = dp[i-1][j];                }                for (j = 0; j < m; j ++) {                    for (k = t; k >= cost[j]; k --) {                        dp[i][k] = max(dp[i][k], dp[i-1][k-cost[j]] + value[j]);                    }                }                break;            case 2:                for (j = 0; j <= t; j ++) {                    dp[i][j] = dp[i-1][j];                }                for (j = 0; j < m; j ++) {                    for (k = t; k >= cost[j]; k --) {                        dp[i][k] = max(dp[i][k], dp[i][k-cost[j]] + value[j]);                    }                }                break;            }        }        if(dp[n][t] < 0) {            printf("-1\n");        } else {            printf("%d\n", dp[n][t]);        }    }    return 0;}

原理掌握后一维的其实和二维的很相似,附上代码:

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int INF = 1 << 29;const int MAXN = 100 + 11;int n, m, sum, g;int w[MAXN], p[MAXN];int dp[MAXN], tmp[MAXN];int main(){    while(~scanf("%d%d", &n, &sum)){        memset(dp, 0, sizeof(dp));        for(int i = 1; i <= n; ++i){            scanf("%d%d", &m, &g);            for(int j = 1; j <= m; ++j){                scanf("%d%d", &w[j], &p[j]);            }            switch(g){            case 0:                for(int j = 0; j <= sum; ++j){                    tmp[j] = dp[j];                    dp[j] = -INF;                }                for(int k = 1; k <= m; ++k){                    for(int j = sum; j >= w[k]; --j){                        dp[j] = max(dp[j], max((dp[j-w[k]]+p[k]), (tmp[j-w[k]] + p[k])));                    }                }                break;            case 1:                for(int j = 0; j <= sum; ++j){                    tmp[j] = dp[j];                }                for(int k = 1; k <= m; ++k){                    for(int j = sum; j >= w[k]; --j){                        dp[j] = max(dp[j], (tmp[j-w[k]] + p[k]));                    }                }                break;            case 2:                for(int j = 0; j <= sum; ++j){                    tmp[j] = dp[j];                }                for(int k = 1; k <= m; ++k){                    for(int j = sum; j >= w[k]; --j){                        dp[j] = max(dp[j], (dp[j-w[k]] + p[k]));                    }                }                break;            }        }        dp[sum] = max(dp[sum], -1);        printf("%d\n", dp[sum]);    }    return 0;}
0 0
原创粉丝点击