POJ 1015 / UVA 323 Jury Compromise(01背包,打印路径)

POJ1015UVA323参考文章
题目大意:给出\(n\)对数,从中选出\(m\)对数,使各对数的差累加和最小的情况下总和最大。
  设每对数的差值为\(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;
}

猜你喜欢

转载自www.cnblogs.com/shuitiangong/p/12671664.html