朝题夕解之动态规划(6)

简介: 朝题夕解之动态规划(6)

题目描述(题目难度:困难)


在一个遥远的国家,一名嫌疑犯是否有罪需要由陪审团来决定。

陪审团是由法官从公民中挑选的。

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

首先,参与诉讼的控方和辩方会给所有候选人打分,分值在 0 到 20 之间。

第 i ii 个人的得分分别记为p [ i ] p[i]p[i] 和 d [ i ] d[i]d[i]。

为了公平起见,法官选出的 M MM 个人必须满足:辩方总分 D DD 和控方总分 P PP 的差的绝对值 |D DD−P PP| 最小。

如果选择方法不唯一,那么再从中选择辨控双方总分之和 D + P D+PD+P 最大的方案。

求最终的陪审团获得的辩方总分 D DD、控方总分P PP,以及陪审团人选的编号。

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


输入格式

输入包含多组测试数据。

每组测试数据第一行包含两个整数 N NN 和 M MM。

接下来 N 行,每行包含两个整数 p [ i ] p[i]p[i] 和 d [ i ] d[i]d[i]。

每组测试数据之间隔一个空行。

当输入数据 N NN=0,M MM=0 时,表示结束输入,该数据无需处理。


输出格式

对于每组数据,第一行输出 Jury #C,C 为数据编号,从 1 开始。

第二行输出 Best jury has value P for prosecution and value D for defence:,P PP 为控方总分,D DD 为辩方总分。

第三行输出按升序排列的陪审人选编号,每个编号前输出一个空格。

每组数据输出完后,输出一个空行。


数据范围

1≤N NN≤200,

1≤M MM≤20

0≤p [ i ] , d [ i ] p[i],d[i]p[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


解题报告


一、题意解读


对于每一个人,分析下来其实只有两种选择的,要么能选他,要么不选他。那么咱们就可以向着01背包的方向思考了。


二、DP分析

微信图片_20221018145103.jpg

三、难点剖析


(1)状态转移方程


注意DP分析图中的集合f [ i , j , k ] f[i,j,k]f[i,j,k]所代表的含义,以及所依赖的最大值属性。才能更好的对状态计算中的状态转移方程进行确定。

image.png

微信图片_20221018145211.jpg

(2)答案输出


DP环节中计算出所有的总和最大的情况。现在需要做的就是去枚举出差值最小的情况。

从f [ n , m , x x x ] f[n,m,xxx]f[n,m,xxx]中枚举,比如看看0存不存在方案,假如存在,说明差值最小可以是0,再枚举1和-1,紧接着2和-2等等。


假如3和-3都存在差值最小的了,那么此时就要去计算是谁的和更大,选择和更大的作为答案输出。


最后还要求我们把选择的人究竟是谁搞出来,需要咱们反推。也就是琢磨当前这个f [ i , j , k ] f[i,j,k]f[i,j,k]是从左边的状态转移过来的了,还是右边的状态转移过来的了。

找DP问题的方案本质从终点开始反推,当前这个状态是从哪个状态转移过来的。很像一个有向无环图去找当前结点的前一个结点,但是注意当前这个结点可能是多个结点指向过来的。


四、算法流程

微信图片_20221018145309.png

参考代码

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
//因为数组中不能存在负数,所以需要把-400~+400映射到大于等于0的范围内,所以加了400的偏移量
//打分范围是0 ~ 20,最多可以选20个人,搭配起来,就可组成-400 到 400中的任意值
const int N = 210, M = 810,base = 400;
int n,m;    //n个人中选m个
int p[N],d[N];//存放控方分数的数组p,和存放辩方分数的数组d
int f[N][21][M];//第一维是人数,第二维是最多选多少个人,第三维是差值总和
int ans[N];   //存放最后答案的方案数目
int main()
{
    int T = 1;
    //多组测试数据,直到读到0和0为止
    while(scanf("%d %d",&n,&m),n||m)
    {
        //读入n个人p和d的值
        for(int i = 1; i <= n;i++) scanf("%d %d",&p[i],&d[i]);
        //初始化f数组,求最大值,所以初始化为负无穷
        memset(f,-0x3f,sizeof f);
        f[0][0][base] = 0;//解决f[0, 0, 0] 这个合法状态
        //用f[i][j][k]表示从前i个人中选j个人且总差为k的所有方案中的总分最大的方案的总分
        for(int i =1; i <= n;i++)
            for(int j = 0; j <= m;j++)
                for(int k = 0; k <= M;k++)
                {
                    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]);
                }
        //到这里为止,就处理好了801种情况中总总和最大各自是多少。现在去从中枚举差值最小的情况
        int v= 0;         //处理最小差值
        //base-v 和 base+v 分别代表负数范围和正数范围
        //差值在负数范围以及在正数范围内都不存在合理数据的时候,就得去更新这个v
        while(f[n][m][base-v] < 0 && f[n][m][base+v] < 0)  v++;
        //比较负数范围内的更大还是正数范围内的更大,筛选一个大点的
        if(f[n][m][base-v] > f[n][m][base+v]) v = base - v;
        else v = base +v;
        //从终点倒着推求方案的过程
        int cnt = 0;
        int i = n,j = m, k = v;
        //当还没有找够m个可选的人的时候,就继续执行
        while(j)
        {
            //如果当前方案是从不选择i的情况转移过来的,那么可以直接扣除i
            if(f[i][j][k] == f[i-1][j][k]) i--;
            else //否则就是从选择i的情况转移过来的
      {
                ans[cnt++] = i;
                k -= (p[i]-d[i]);
                i--, j--;
            }
        }
        int sp = 0, sd = 0;
        //枚举所有被选中的人,把p的总和以及d的总和求出了
        for(int i = 0; i < cnt ;i++)
        {
            sp += p[ans[i]];
            sd += d[ans[i]];
        }
        //输出答案
        printf("Jury #%d\n",T++); //输出数据组号
        printf("Best jury has value %d for prosecution and value %d for defence:\n",sp,sd);
        sort(ans, ans + cnt);
        //所有选择出来的人的编号
        for(int i = 0 ; i < cnt ;i++) printf(" %d",ans[i]);
        puts("\n");
    }
    return 0;
}


相关文章
|
4天前
|
算法 测试技术 C++
|
4天前
|
C++ NoSQL 容器
动态规划二
动态规划二
|
4天前
|
C++ NoSQL 容器
动态规划三
动态规划三
|
7月前
|
算法
【学会动态规划】按摩师(11)
【学会动态规划】按摩师(11)
24 0
|
4天前
动态规划1
动态规划1
15 0
动态规划1
|
8月前
|
存储
【动态规划】
【动态规划】
|
7月前
|
机器学习/深度学习 算法
动态规划详解
前段时间一直在做关于数据结构的题,也算是对数据结构有了一定的了解,知道了有些数据结构的基本算法。现在刚刚开始接触动态规划,其实写这篇文章的初衷是一来锻炼一下自己的总结能力,二来也是希望通过这篇文章,来指引和我一样的初学者,废话不多说了,开始吧。
34 0
动态规划-子序列问题
前言 在上篇文章我们学习了动态规划的公共子序列问题,现在我们来学习下动态规划的单字符串子序列问题。
|
存储
动态规划最大字段和
动态规划最大字段和
67 0