2019.9.29 陪审团

题目描述

陪审团制度是由法官从公民中选出N人作为陪审团候选人。然后由原被告双方从中选出M人组成陪审团,由陪审团决定是否定罪。
首先,参与诉讼的双方给所有候选人打分,分值在0到20之间。第i个人得分分别记为a[i]和b[i]。为了公平起见,法官选出的M人必须满足原告打分总和D和控方打分总和P的差的绝对值|D-P|最小。如果选择方法不唯一,再从中选出D+P的值最大的方案。

输入

第一行有2个用空格隔开的整数n和m,便是候选人。

接下来的n行每行为用空格隔开的两个数,分别表示a[i]和b[i]。

输出

输出包含两行,第一行为两个数D和P,见题意描述;第二行按照从小到大的顺序输出m个候选人的编号。

样例输入
4 2
1 2
2 3
4 1
6 2
样例输出
6 4
2 3
提示

【数据范围】

100%的数据满足:1<=n<=200,1<=m<=20


我们很容易看出这个题是dp,但是需要用到各种小技巧。;
首先状态表示:这个题目中一共有两个有用的变量:辩方与控方的差(下文简称辩控差)、辩方与控方的和(下文简称辩控和),加上用来控制转移顺序的一维,我们得到了状态表示的方法:
dp[i][j]表示选了i个人、辩控差是j的时候的辩控和。
 
上文说到,dp的第一维i容易看出是用来控制转移顺序的,所以我们的转移顺序是从已经选了i个人------>已经选了i+1个人。
让我们分析一下这个转移的条件:
(1)因为每个人只能被选一次,所以新选出来的这个人一定不能选过;
(2)因为要求辩控和最大,所以要满足新的辩控差大于原来的辩控差。
假设我们记对于第i个人辩方的得分是a[i],控方的得分是b[i],我们得到转移方程:
(如果j没有被选已经选过的前i个人中选过且dp[i][k]+a[j]+b[j]>dp[i+1][k-a[j]+b[j))更新这个dp值
注意由于我们计算辩控差时严格按照辩方得分-控方得分进行计算,所以我们设置一个标准值stand=20*m,则dp数组的第二维表示正确的辩方得分-控方得分+stand。
 
关于怎么输出选的人数以及查找第j个人有没有已经选过?
我们用一个数组path[i][j]表示选了i个人辩控差是j且辩控和最大时候第i个人选的是谁;这样我们可以从后往前找到每一步选的是谁并输出,也可以一步一步往前找到之前有没有选过一个指定的人。
怎么输出D和P?我们可以知道最终的辩控差是stand+k时辩控差最小且辩控和最大,(假设我们用a数组记录辩方每人得分,b数组记录控方每人得分),则k应该等于(所有选出的a[i]之和-所有选出的b[i]之和)+stand,根据和差原理,我们要求出(所有选出的a[i]之和),必须知道(所有选出的a[i]之和-所有选出的b[i]之和)和(所有选出的a[i]之和+所有选出的b[i]之和)。而我们知道第一个是k-stand,第二个是dp[m][k],所以我们将这两项加起来再除以二即可得到辩方分数总和,控方同理。
上代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
int n,m,a[10050],b[10050],dp[1050][1050],path[1050][1050],stand,ans[1050];
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i],&b[i]);
    memset(dp,-1,sizeof dp);//标记所有状态均为不可行
    stand=20*m;
    dp[0][stand]=0;//辩控差为0、选了0个人时最大辩控和为0
    for(int j=0;j<m;j++)//枚举已经选了几个人
    {
        for(int k=0;k<=stand*2;k++)
        {
            if(dp[j][k]>=0)//如果这种状态可行
            {
                for(int i=1;i<=n;i++)//再选一个
                {
                    if(dp[j][k]+a[i]+b[i]>dp[j+1][k+a[i]-b[i]])//比较辩控和
                    {
                        int t1=j,t2=k;
                        while(t1>0&&path[t1][t2]!=i)
                        {
                            t2-=a[path[t1][t2]]-b[path[t1][t2]];
                            --t1;
                        }//倒序查找i有没有被前j个人选过
                        if(!t1)
                        {
                            dp[j+1][k+a[i]-b[i]]=dp[j][k]+a[i]+b[i];
                            path[j+1][k+a[i]-b[i]]=i;
                        }//如果没有选过,记录路径和辩控和
                    }
                }
            }
        }
    }
    int j=0;
    while(dp[m][stand+j]<0&&dp[m][stand-j]<0)++j;//双向查找直到找到一种方案可行
    int k=(dp[m][stand+j]>dp[m][stand-j])?stand+j:stand-j;//比较绝对值一样时两种辩控和大小
    printf("%lld %lld\n",(k-stand+dp[m][k])/2,(stand-k+dp[m][k])/2);//和差原理求D和P
    int t=k;
    for(int i=1;i<=m;i++)
    {
        ans[i]=path[m-i+1][t];
        t-=a[path[m-i+1][t]]-b[path[m-i+1][t]];
    }//逆序查找选过哪些人
    sort(ans+1,ans+m+1);//按字典序排序
    for(int i=1;i<=m;i++)printf("%lld ",ans[i]);//输出答案
    return 0;
}

 

转载于:https://www.cnblogs.com/qxds/p/11609769.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值