poj1015 正解--二维DP(完全背包)

题目链接:http://poj.org/problem?id=1015

错误解法:

网上很多解法是错误的,用dp[i][j]表示选择i个人差值为j的最优解,用path[i][j]存储路径,循环次序为“选的第几个人->选哪个人->差值之和”或者“选的第几个人->差值之和->选哪个人”,为了避免选择重复的人需要判断。错误的原因是存储路径的方式使得会覆盖一些情况,比如1 3 5和2 4 6均满足dp[3][k]最优时,若采用2 4 6作为dp[3][k]的最优解,而1 3 5 6是最终答案,那么此时6已经被dp[3][k]选择了,则得不到最终答案。

比如这组数据:

9 6
6 2
16 10
4 9
19 8
17 12
4 7
10 2
2 14
5 18
0 0
这组数据的正确答案是
Jury #1
Best jury has value 54 for prosecution and value 54 for defence:
1 2 3 4 6 9
但是错误程序的答案是
Jury #1
Best jury has value 52 for prosecution and value 52 for defence:
 1 3 4 5 6 8
但由于poj这题的数据较弱,故这种解法也可以AC,uva323那道的数据较强,就会卡这种做法,错误解的代码如下:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cmath>
 5 using namespace std;
 6 
 7 int n,m,p,d,cas=1,fix,S,A,D,P;
 8 int dp[25][900],path[25][900],sub[205],add[205],res[25];
 9 
10 bool is(int i,int j,int k){
11     while(i>0){
12         if(path[i][j]==k)
13             return false;
14         j=j-sub[path[i][j]];i=i-1;
15     }
16     return true;
17 }
18 
19 int main(){
20     while(scanf("%d%d",&n,&m)!=EOF&&n){
21         memset(dp,-1,sizeof(dp));
22         memset(path,0,sizeof(path));
23         fix=20*m;
24         dp[0][fix]=0;
25         for(int i=1;i<=n;i++){
26             scanf("%d%d",&p,&d);
27             sub[i]=p-d;
28             add[i]=p+d;
29         }
30         for(int i=0;i<m;i++)
31             for(int j=0;j<=2*fix;j++)
32                 if(dp[i][j]>=0)
33                     for(int k=1;k<=n;k++)
34                         if(is(i,j,k)&&dp[i][j]+add[k]>dp[i+1][j+sub[k]]){
35                             dp[i+1][j+sub[k]]=dp[i][j]+add[k];
36                             path[i+1][j+sub[k]]=k;
37                         }
38         int key;
39         for(key=0;key<=fix;key++)
40             if(dp[m][fix+key]>=0||dp[m][fix-key]>=0)
41                 break;
42         S=dp[m][fix+key]>dp[m][fix-key]?fix+key:fix-key;
43         A=dp[m][S];        
44         for(int i=m,j=S;i>0;){
45             res[i]=path[i][j];
46             j=j-sub[res[i]];
47             i--;
48         }
49         sort(res+1,res+m+1);
50         P=(A+(S-fix))/2;D=(A-(S-fix))/2;
51         printf("Jury #%d\n",cas++);
52         printf("Best jury has value %d for prosecution and value %d for defence:\n",P,D);
53         for(int i=1;i<=m;i++)
54             printf(" %d",res[i]);
55         printf("\n\n");
56     }
57     return 0;
58 }
View Code

 正解:

把循环次序改成“选哪个人->选的第几个人->差值之和”,并且使用vector<int> path[25][805]存储路径,从而可以存储所有情况,无法理解的话,就举个例子模拟模拟,而且是由于按照顺序遍历,最后的路径本身就是有序的。因为差值可能为负值,需要加一个修正值fix=m*20。循环部分就是完全背包模型,每个人的重量为1,背包重量为m,从1到n遍历每个人是否被选中,从m-1到0遍历背包的空间(因为每个人最多选一次,故倒序遍历)。详见代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<vector>
 5 using namespace std;
 6 
 7 int n,m,cas=1,fix,p,d,S,A,P,D;
 8 int dp[25][805],sub[205],add[205];
 9 vector<int> path[25][805];
10 
11 int main(){
12     while(~scanf("%d%d",&n,&m)&&n){
13         memset(dp,-1,sizeof(dp));
14         for(int i=1;i<=m;++i)
15             for(int j=0;j<805;++j)
16                 path[i][j].clear();
17         fix=20*m;
18         dp[0][fix]=0;
19         for(int i=1;i<=n;++i){
20             scanf("%d%d",&p,&d);
21             sub[i]=p-d;
22             add[i]=p+d;
23         }        
24         for(int i=1;i<=n;++i)
25             for(int j=m-1;j>=0;--j)
26                 for(int k=0;k<=2*fix;++k)
27                     if(dp[j][k]>=0)
28                         if(dp[j][k]+add[i]>dp[j+1][k+sub[i]]){
29                             dp[j+1][k+sub[i]]=dp[j][k]+add[i];
30                             path[j+1][k+sub[i]]=path[j][k];
31                             path[j+1][k+sub[i]].push_back(i);
32                         }
33         int kk;
34         for(kk=0;kk<=fix;++kk)
35             if(dp[m][fix+kk]>=0||dp[m][fix-kk]>=0)
36                 break;
37         S=dp[m][fix+kk]>dp[m][fix-kk]?fix+kk:fix-kk;
38         A=dp[m][S];        
39         P=(A+(S-fix))/2,D=(A-(S-fix))/2;
40         printf("Jury #%d\n",cas++);
41         printf("Best jury has value %d for prosecution and value %d for defence:\n",P,D);
42         for(int i=0;i<m;++i)
43             printf(" %d",path[m][S][i]);
44         printf("\n\n");        
45     }
46     return 0;
47 }

 

posted @ 2019-02-23 15:45  Frank__Chen  阅读(1345)  评论(0编辑  收藏  举报