题目描述
陪审团制度是由法官从公民中选出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; }