题意:在Frobnia,一个遥远的国家,法庭审判的判决是由普通市民组成的陪审团决定的。每次审判开始前,都要挑选出一个陪审团。首先,随机从公众中抽取几个人。控辩双方指定一个0到20的分数表示对这些人的偏好。0意味着非常不喜欢,20意味着非常适合进入陪审团。法官根据双方给出的分数,决定陪审团的组成。为了确保一个公正的审判,陪审团的偏袒倾向应尽可能的被平衡。因此,陪审团应在控辩双方都满意的方式下挑选出来。现在我们要将这个挑选行为更精确化:给出n个候选陪审员,对于任意候选陪审员i,给出两个值di(辩方给出的分数)和pi(控方给出的分数),你要挑选出一个m人的陪审团。如果J是一个具有m个元素的子集{1.....n},那么D(J)=sum(dk),k属于J,还有P(J)=sum(pk),k属于J,分别表似控辩双方给出的总分。对于一个最佳的陪审团J,|D(J)-P(J)|必须是最小的。如果有多个方案满足|D(J)-P(J)|最小,那么应该选择D(J)+P(J)值最大的方案,因为陪审团应尽可能的符合控辩双方理想中的选择。要求你写一个程序实现陪审团的挑选过程并从给出的人选中选择出最佳的陪审团。
算法:三维dp
/* 算法:三维dp 定义dp[i,j,k]为前i个候选人中选择j个陪审员取得最小辩控差为k时最大的辩控和 ,我们要求的是dp[n,m,min_diff] 如何得到dp[i,j,k]呢? 考虑i-1时,为了得到i的情况: 1.如果dp[i-1][j-1][k-difference[i]]存在,而且dp[i-1][j-1][k-difference[i]]+sum[i]>dp[i,j,k], 那么,我们选择第i个候选人,即 dp[i,j,k] = dp[i-1][j-1][k-difference[i]]+sum[i] 2.如果 dp[i-1][j-1][k-difference[i]]不存在,或者说不可达,那么我们取dp[i,j,k] = dp[i-1,j,k] 即dp[i][j][k] = max{dp[i-1,j,k] , dp[i-1][j-1][k-difference[i]]+sum[i]} */ #include <iostream> #include <string.h> #include <algorithm> using namespace std; const int MAX_N=201; // 候选人最大数 const int MAX_M=21; // 陪审团人数最大数 const int MAX_DIFF=801; int sum[MAX_N]; // sum[i] = d[i] + p[i] int difference[MAX_N]; // difference[i] = d[i] - p[i] int p[MAX_N]; int d[MAX_N]; int dp[MAX_N][MAX_M][MAX_DIFF]; // dp[i][j][k]=q:前i个候选人选j个陪审员最小辩控差为k时最大辩控和为q int path[MAX_N][MAX_M][MAX_DIFF]; // path[i][j][k]=p:使dp[i][j][k]=q时选择的候选人是p int ans[MAX_M]; // 存放最终的结果 int main() { int n,m; int cases = 1; while (cin >> n >> m && n > 0 && m > 0) { memset(dp,-1,sizeof(dp)); for (int i=1; i<=n; i++) { cin >> p[i] >> d[i]; sum[i] = p[i] + d[i]; difference[i] = p[i] - d[i]; } // sum{sum[1..n}:最小为-400 int max_diff = m * 20 ; // 因辩控差的范围是[-20*m,20*m],如果整个数组都加上20*m,其范围就变为[0,40*m] // 那么,原来的dp[i][0][0],自然变成 dp[i][0][max_diff] for (int i=0; i<=n; i++) { dp[i][0][max_diff] = 0; } // dp[i,j,k]=max{dp[i-1,j,k],dp[i-1,j-1,k-difference[i]]+sum[i]} for (int i=1; i<=n; i++) { for (int j=1; j<=m && j<=i; j++) { for (int k=0; k<=max_diff*2; k++) { dp[i][j][k]=dp[i-1][j][k]; path[i][j][k]=path[i-1][j][k]; if (k>=difference[i] && k-difference[i]<=max_diff*2 && dp[i-1][j-1][k-difference[i]] >= 0 ) { if (dp[i-1][j-1][k-difference[i]]+sum[i] > dp[i][j][k]) { dp[i][j][k] = dp[i-1][j-1][k-difference[i]]+sum[i]; path[i][j][k] = i; } } } } } // 输出结果 int i,min_diff; for (i=0; i<=max_diff; i++) { if (dp[n][m][max_diff-i]>=0 || dp[n][m][max_diff+i]>=0) { break; } } (dp[n][m][max_diff-i] > dp[n][m][max_diff+i])? min_diff = max_diff-i : min_diff = max_diff+i; cout<<"Jury #"<<cases++<<endl << "Best jury has value "; cout<<(dp[n][m][min_diff]+min_diff-max_diff)/2<<" for prosecution and value "; cout<<(dp[n][m][min_diff]-min_diff+max_diff)/2<<" for defence:"<<endl; // dp[i][j][k] = dp[i-1][j-1][k-difference[i]]+sum[i]; // path[i][j][k] = i; for (int i=n,j=m,k=min_diff; j>=1;) { int p = path[i][j][k] ; ans[j] = p; k -= difference[p]; j--; i = path[p-1][j][k]; } for (int i=1; i<=m; i++) { cout << " " << ans[i] ; } printf("\n\n"); } }
我们还可以使用滚动数据将三维dp改为二维dp,切记要逆序枚举。
/* 算法:背包思想,对于第i个人,可以选择加入陪审团,也可以选择不加入。 定义:dp[i][j]=k,候选人数为i最小辩控差为j时取得的最大辩控差为k 考虑i-1时如何得到i: 1.如果dp[i-1][j-difference[i]]可以取到,而且 dp[i-1][j-difference[i]]+sum[i] > dp[i][j] ,我们此时选择i 令 dp[i][j] = dp[i-1][j-difference[i]]+sum[i] 2.否则,等于它自己 即 dp[i][j] = max{dp[i][j],dp[i-1][j-difference[i]]+sum[i]} */ #include <iostream> #include <string.h> #include <vector> using namespace std; const int MAX_N=201; // 候选最大人数 const int MAX_M=21; // 陪审团人数最大数 const int MAX_DIFF=801; // 最大差值 int sum[MAX_N]; // sum[i] = d[i] + p[i] int difference[MAX_N]; // difference[i] = d[i] - p[i] int p[MAX_N]; int d[MAX_N]; int dp[MAX_N][MAX_DIFF]; int ans[MAX_M]; int main() { int n,m; int cases = 1; while (cin >> n >> m && n > 0 && m > 0) { memset(dp,-1,sizeof(dp)); memset(p,0,sizeof(p)); memset(d,0,sizeof(d)); memset(sum,0,sizeof(sum)); memset(difference,0,sizeof(difference)); vector<int> path[21][801]; for (int i=1; i<=n; i++) { cin >> p[i] >> d[i]; sum[i] = p[i] + d[i]; difference[i] = p[i] - d[i]; } // sum{difference[1..n}:最小为-400 int max_diff = m * 20 ; // 如果选中i个人... dp[0][max_diff] = 0; for (int i=1; i<=n; i++) { // 此处要逆序枚举,至于原因,请参见 http://blog.csdn.net/aidway/article/details/50726472 for (int j=m; j>=1; j--) { for (int diff=0; diff<=max_diff*2; diff++) { if (dp[j-1][diff] >= 0) { if (dp[j-1][diff]+sum[i] > dp[j][diff+difference[i]]) { dp[j][diff+difference[i]]=dp[j-1][diff]+sum[i]; path[j][diff+difference[i]]=path[j-1][diff]; path[j][diff+difference[i]].push_back(i); } } } } } // 输出结果 int i,min_diff; for (i=0; i<=max_diff; i++) { if (dp[m][max_diff-i]>=0 || dp[m][max_diff+i]>=0) { break; } } (dp[m][max_diff-i] > dp[m][max_diff+i])? min_diff = max_diff-i : min_diff = max_diff+i; cout<<"Jury #"<<cases++<<endl << "Best jury has value "; cout<<(dp[m][min_diff]+min_diff-max_diff)/2<<" for prosecution and value "; cout<<(dp[m][min_diff]-min_diff+max_diff)/2<<" for defence:"<<endl; int tmp = m; for( i=0; i < m; i++ ) { cout << " " << path[m][min_diff][i]; } cout << endl; } }
如果不小心没有逆序枚举,就会出现如下的情况,虽然在poj上可以AC,但其实是错误的实现方法:
/* 请注意:虽然可以AC,但该实现方法是错误的!!! 有数据为证: 9 6 6 2 16 10 4 9 19 8 17 12 4 7 10 2 2 14 5 18 0 0 正确答案: Best jury has value 54 for prosecution and value 54 for defence: 1 2 3 4 6 9 错误答案: Best jury has value 52 for prosecution and value 52 for defence: 1 3 4 5 6 8 */ #include <iostream> #include <string.h> #include <algorithm> using namespace std; const int MAX_N=201; // 候选最大人数 const int MAX_M=21; const int MAX_DIFF=801; int sum[MAX_N]; int difference[MAX_N]; int p[MAX_N]; int d[MAX_N]; int dp[MAX_N][MAX_DIFF]; int path[MAX_M][MAX_DIFF]; int ans[MAX_M]; bool selected(int i, int diff, int j) { while (i > 0 && path[i][diff] != j) { diff -= difference[path[i][diff]]; i--; } return (i > 0) ; } int main() { int n,m; int cases = 1; while (cin >> n >> m && n > 0 && m > 0) { memset(dp,-1,sizeof(dp)); memset(p,0,sizeof(p)); memset(d,0,sizeof(d)); memset(path,0,sizeof(path)); memset(sum,0,sizeof(sum)); memset(difference,0,sizeof(difference)); for (int i=1; i<=n; i++) { cin >> p[i] >> d[i]; sum[i] = p[i] + d[i]; difference[i] = p[i] - d[i]; } int max_diff = m * 20 ; dp[0][max_diff] = 0; for (int i=1; i<=m; i++) { // 对每一辩控差 for (int diff=0; diff<=max_diff*2; diff++) { if (dp[i-1][diff] >= 0) { // 枚举每个人 for (int j=1; j<=n; j++) { if (dp[i-1][diff]+sum[j] > dp[i][diff+difference[j]]) { if (!selected(i-1,diff,j)) { dp[i][diff+difference[j]] = dp[i-1][diff]+sum[j]; path[i][diff+difference[j]] = j; } } } } } } int i,min_diff; for (i=0; i<=max_diff; i++) { if (dp[m][max_diff-i]>=0 || dp[m][max_diff+i]>=0) { break; } } (dp[m][max_diff-i] > dp[m][max_diff+i])? min_diff = max_diff-i : min_diff = max_diff+i; cout<<"Jury #"<<cases++<<endl; cout<<"Best jury has value "; cout<<(dp[m][min_diff]+min_diff-max_diff)/2<<" for prosecution and value "; cout<<(dp[m][min_diff]-min_diff+max_diff)/2<<" for defence:"<<endl; int tmp = m; while (tmp > 0) { ans[tmp] = path[tmp][min_diff]; min_diff -= difference[path[tmp][min_diff]]; tmp--; } sort(ans,ans+m+1); for (int t=1; t<=m; t++) { cout << " " << ans[t] ; } cout << endl << endl; } }