朝题夕解之动态规划(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;
}


相关文章
|
网络协议 安全 网络安全
图解OSI七层模型,2024最强科普!
【7月更文挑战第20天】
3323 2
图解OSI七层模型,2024最强科普!
|
图形学
【Unity小技巧】Unity中实现带有Sprite Shape的2D水效果(附项目源码)
【Unity小技巧】Unity中实现带有Sprite Shape的2D水效果(附项目源码)
1387 0
|
弹性计算 网络安全
阿里云服务器怎么换IP?分两种情况
阿里云服务器怎么换IP?分两种情况
994 1
|
存储 开发工具 git
idea中git检出失败
idea中git检出失败
145 0
|
存储 前端开发 rax
linux内核1-GNU汇编入门_X86-64&ARM(上)
linux内核1-GNU汇编入门_X86-64&ARM
|
SQL 关系型数据库 MySQL
MySQL学习笔记-事务的隔离性
MySQL学习笔记-事务的隔离性
128 0
|
存储 算法 Java
探索Java数组:基础、特性与灵活应用
在Java编程中,数组是一种基础而重要的数据结构,它能够以紧凑的方式存储多个元素。无论是在简单的数据存储还是复杂的算法实现中,数组都扮演着不可或缺的角色。本文将引导您深入了解Java数组,包括数组的基本概念、特性、用法以及常见应用场景。
|
传感器 编解码 算法
用于分析脉冲类信号的二阶瞬态提取变换研究(Matlab代码实现)
用于分析脉冲类信号的二阶瞬态提取变换研究(Matlab代码实现)
295 0
|
IDE 数据挖掘 Linux
答编程教室同学问
在课程一开始,我推荐了大家使用 python 自带的IDE -- IDLE。因为你不需要再做更多的安装和配置,就可以用它来写 python 程序。虽然方便,但从长远来看,它不是一个很好的解决方案,随着你的能力提升迟早会要抛弃它。