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 }
正解:
把循环次序改成“选哪个人->选的第几个人->差值之和”,并且使用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 }