<<算法竞赛进阶指南>>:陪审团

该博客讨论了一个关于陪审团选择的问题,其中涉及01背包问题和动态规划算法。问题要求选出一组人,使得辩方和控方的评分差值绝对值最小,并且总分之和最大。博主给出了详细的动态规划状态转移方程,以及如何通过回溯获取具体的选择方案。最后,博主提供了一个完整的C++代码实现来解决这个问题。

在一个遥远的国家,一名嫌疑犯是否有罪需要由陪审团来决定。
陪审团是由法官从公民中挑选的。

法官先随机挑选 NNN 个人(编号 1,2…,N1,2…,N1,2,N)作为陪审团的候选人,然后再从这 NNN 个人中按照下列方法选出 MMM 人组成陪审团。

首先,参与诉讼的控方和辩方会给所有候选人打分,分值在 000202020 之间。
iii 个人的得分分别记为 p[i]p[i]p[i]d[i]d[i]d[i]。 为了公平起见,法官选出的 MMM 个人必须满足:辩方总分 DDD 和控方总分 PPP 的差的绝对值 ∣D−P∣|D−P|DP 最小。
如果选择方法不唯一,那么再从中选择辨控双方总分之和 D+PD+PD+P 最大的方案。求最终的陪审团获得的辩方总分 DDD、控方总分 PPP,以及陪审团人选的编号。

注意:若陪审团的人选方案不唯一,则任意输出一组合法方案即可。

输入格式:

输入包含多组测试数据。
每组测试数据第一行包含两个整数 NNNMMM
接下来 NNN 行,每行包含两个整数 p[i]p[i]p[i]d[i]d[i]d[i]
每组测试数据之间隔一个空行。
当输入数据 N=0,M=0N=0,M=0N=0M=0 时,表示结束输入,该数据无需处理。

输出格式:
对于每组数据,第一行输出 KaTeX parse error: Expected 'EOF', got '#' at position 6: Jury #̲CCCC
为数据编号,从 111 开始。
第二行输出 BestjuryhasvaluePforprosecutionandvalueDfordefence:Best jury has value P for prosecution and value D for defence:BestjuryhasvaluePforprosecutionandvalueDfordefence:PPP为控方总分,DDD 为辩方总分。
第三行输出按升序排列的陪审人选编号,每个编号前输出一个空格。
每组数据输出完后,输出一个空行。

数据范围:
1≤N≤2001≤N≤2001N200
1≤M≤201≤M≤201M20
0≤p[i],d[i]≤200≤p[i],d[i]≤200p[i],d[i]20

输入样例:
4 2
1 2
2 3
4 1
6 2
0 0

输出样例:

Jury #1
Best jury has value 6 for prosecution and value 4 for defence:
2 3

首先 遇到背包问题, 确定背包类型, 每个人只能选一个, 故为 010101 背包, 然后题目要求具体方案, 首先答案要求满足两个条件, 第一是要 ∣D−P∣|D - P|DP 最小,其次是 D+PD + PD+P 最大, 我们可以将 D+PD + PD+P 定义为背包的属性

状态定义: f[i][j][k]f[i][j][k]f[i][j][k]: 在前 iii 个人中选, 总人数不超过 jjj, 并且总差值为 kkk 的集合
状态属性: D+PD + PD+P 的最大值
状态计算: 选第 iii 个人 不选第 iii 个人
f[i][j][k]=max(f[i−1][j][k],f[i−1][j−1][k−(p[i]−d[i])]+p[i]+d[i]);f[i][j][k] = max(f[i - 1][j][k], f[i - 1][j - 1][k - (p[i] - d[i])] + p[i] + d[i]);f[i][j][k]=max(f[i1][j][k],f[i1][j1][k(p[i]d[i])]+p[i]+d[i]);

求出 D+PD + PD+P 的最大值并不难, 难点在于如何得到具体方案,我们可以通过方向搜一遍, 如果从终点状态,一直迭代到起始状态,能够进行状态转移,我们就记下这个方案

注意: 由于p[i] 是从 111 ~ 202020, 所以 p[i]−d[i]p[i] - d[i]p[i]d[i] 是从 −40-4040 ~ 404040, 所以总的 p[i]−d[i]p[i] - d[i]p[i]d[i] 是从 −400-400400 ~ 400400400, 但是如果直接使用,会导致数组越界,所以我们加上一个偏移量取名为 basebasebase,其值 400400400, 故差值的范围就会变成 000 ~ 800800800

#include<bits/stdc++.h>

using namespace std;

const int N = 210, M = 810, base = 400;

int f[N][21][M], p[N], d[N], ans[N], n, m;

int main()
{
    
    int T = 1;
    while(cin >> n >> m, n, m)
    {
        for(int i = 1; i <= n; i ++ ) cin >> p[i] >> d[i];  //读入
        
        memset(f, -0x3f, sizeof f);   //因为求最大值, 故赋值为负无穷
        
        f[0][0][base] = 0;  //初始化为0
        int cnt = 0;   //初始化
        
        for(int i = 1; i <= n; i ++)  //枚举前i个人
        for(int j = 0; j <= m; j ++) //枚举已经选了的人
        for(int k = 0; k < M; k ++ ) //枚举差值 0~801
        {
            f[i][j][k] = f[i - 1][j][k];  //不选第 i 个人
            int t = k - (p[i] - d[i]);   //计算出差值,看是否符合题意
            if(t < 0 || t > M ) continue;   
            if(j < 1) continue;  //是否可以转移
            f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][t] + p[i] + d[i]);   //状态转移
        }
        
        int v = 0;
        while( f[n][m][base + v] < 0 && f[n][m][base - v] < 0) v ++;
        // 当v小于0和v大于0 都不合题意是, v++;
        if(f[n][m][base + v] > f[n][m][base - v]) v = base + v;
        else v = base - v;
        //当v,-v都存在时,取最大值
        int j = m, i = n, k = v;
        
        while(j)
        {
            if(f[i][j][k] == f[i - 1][j][k]) i --;//如果可以不选第i个人
            else 
            {
                ans[cnt ++ ] = i;  //选了第i个人
                k -= p[i] - d[i];  //差值减小
                
                i --, j --; //减小
            }
        }
        
        int a = 0, b = 0;
        for(int i = 0; i < cnt; i ++ ) a += p[ans[i]], b += d[ans[i]];
        //记录答案
        printf("Jury #%d\n", T++);
        printf("Best jury has value %d for prosecution and value %d for defence:\n", a, b);
        
        sort(ans, ans + cnt);  //排序,保证编号是从小到大的
        //打印方案
        for(int i = 0; i < cnt; i ++ ) cout << ans[i] << ' ';
        
        cout << '\n';
        cout << '\n';
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广西小蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值