POJ 1015 / UVA 323 Jury Compromise(01背包,打印路径)
POJ1015 UVA323 参考文章
题目大意:给出\(n\)对数,从中选出\(m\)对数,使各对数的差累加和最小的情况下总和最大。
(博主看了几篇文章,但是都没有考虑到背包容量的越界问题,关于这个,会在代码里解释。顺便提一下,POJ这题的数据是比较弱的,建议去UVA 323检测一下代码的正确性)
设每对数的差值为\(sub[i]\),和为\(sum[i]\)。要从中选出m个数,对于每个数来说,都有选与不选两种情况,所以能不能用背包来做呢?用背包的话还得确定容量。既然题目要求\(sub\)的总和最小的情况下\(sum\)最大,那么我们可以用差值\(sub\)来做容量,求每个差值下的最大的总和。
但是这里一个问题,\(sub\)有可能是负的呀,为了解决这个问题我们可以让容量整体\(+400\)从\([-400,400]\)变成\([0,800]\)(为什么是这个数?因为最多选20对,每对最大差值20,最小-20),那么状态转移方程是\(dp[j][k] = dp[j-1][k-sub[i]]+sum[i]\)(\(i\)为物品编号,\(j\)为选出的第\(j\)个物品,\(k\)为容量。)
然后题目还要求输出路径,这里博主水平有限,只会用\(vector\)来硬存每个差值下的路径。既然这一次的状态是由下一次的状态转移过来的,那么路径就是上一个状态选的物品再加上这一个状态选择的物品,如果从小到大的编号来\(dp\)的话,那么顺序自然就是升序的了。
//https://www.cnblogs.com/shuitiangong/
const int maxn = 2e2+10;
int n, m, kase = 1, sum[maxn], sub[maxn];
int dp[25][805];
vector<int> p[25][805];
int main(void) {
while(~scanf("%d%d", &n, &m) && (n||m)) {
for (int i = 1, d, p; i<=n; ++i) {
scanf("%d%d", &d, &p);
sum[i] = d+p; sub[i] = d-p;
}
//因为存在负数所以整体+400,以400为0并初始化0位dp的起点
memset(dp, -1, sizeof(dp)); dp[0][400] = 0;
for (int i = 1; i<=n; ++i)
for (int j = m; j>=1; --j)
for (int k = min(800, 800+sub[i]); k >= max(0, sub[i]); --k)
//这里要说明一下k = min(800, 800+sub[i])是为了防止sub[i]为正的时候超出800
//同样k >= max(0, sub[i])则是为了防止sub[i]为负的时候小于0
//顺便再提一下k不管从高到低还是从低到高都行,因为有第一维的存在所以并没有后效性
if (dp[j-1][k-sub[i]]>=0 && dp[j][k] < dp[j-1][k-sub[i]]+sum[i]) {
dp[j][k] = dp[j-1][k-sub[i]]+sum[i];
p[j][k] = p[j-1][k-sub[i]];
p[j][k].push_back(i);
}
int delta = 0;
//从0(400)开始向两边枚举,求最小差值
while(dp[m][400+delta]<0 && dp[m][400-delta]<0) ++delta;
delta = dp[m][400+delta] >= dp[m][400-delta] ? 400+delta : 400-delta;
int sumd = (dp[m][delta]+delta-400)/2;
int sump = (dp[m][delta]-delta+400)/2;
printf("Jury #%d\nBest jury has value %d for prosecution and value %d for defence:\n", kase++, sumd, sump);
for (int i = 0; i<m; ++i) printf(i==m-1 ? " %d\n\n" : " %d", p[m][delta][i]);
//因为p第0行始终是空的,所以其实在上面的dp过程中j=1的时候已经完成清空了,所以下面一段可以省略
for (int i = 0; i<=20; ++i)
for (int j = 0; j<=800; ++j)
p[i][j].clear();
}
return 0;
}