zoukankan      html  css  js  c++  java
  • 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
    ",(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;
    }
  • 相关阅读:
    bzoj2124 等差子序列(树状数组+hash)
    CF817F MEX Queries(线段树上二分)
    [USACO12MAR]摩天大楼里的奶牛(状态压缩DP)
    CF786B Legacy(线段树优化建图)
    绿豆蛙的归宿
    单选错位
    聪聪和可可
    Tyvj1952 Easy
    OSU!
    弱题
  • 原文地址:https://www.cnblogs.com/qxds/p/11609769.html
Copyright © 2011-2022 走看看