(POJ 1015) Jury Compromise 经典dp问题 (n选m)

2024-02-05 02:38

本文主要是介绍(POJ 1015) Jury Compromise 经典dp问题 (n选m),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载请注明出处:優YoU http://blog.csdn.net/lyy289065406/article/details/6671105

大致题意:
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的 |D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。
输出:
选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值P,并升序输出他们的编号。

解题思路:
动态规划。

  为叙述问题方便,现将任一选择方案中,辩方总分和控方总分之差简称为“辩控差”,辩方总分和控方总分之和称为“辩控和”。第i 个候选人的辩方总分和控方总分之差记为V(i),辩方总分和控方总分之和记为S(i)。

现用dp(j, k)表示,取j 个候选人,使其辩控差为k 的所有方案中,辩控和最大的那个方案(该方案称为“方案dp(j, k)”)的辩控和。
并且,我们还规定,如果没法选j 个人,使其辩控差为k,那么dp(j, k)的值就为-1,也称方案dp(j, k)不可行。本题是要求选出m 个人,那么,如果对k 的所有可能的取值,求出了所有的dp(m, k) (-20×m≤ k ≤ 20×m),那么陪审团方案自然就很容易找到了。
问题的关键是建立递推关系。需要从哪些已知条件出发,才能求出dp(j, k)呢?显然,方案dp(j, k)是由某个可行的方案dp(j-1, x)( -20×m ≤ x ≤ 20×m)演化而来的。
可行方案dp(j-1, x)能演化成方案dp(j, k)的必要条件是:存在某个候选人i,i 在方案dp(j-1, x)中没有被选上,且x+V(i) = k。在所有满足该必要条件的dp(j-1, x)中,选出 dp(j-1, x) + S(i) 的值最大的那个,那么方案dp(j-1, x)再加上候选人i,就演变成了方案 dp(j, k)。
这中间需要将一个方案都选了哪些人都记录下来。不妨将方案dp(j, k)中最后选的那个候选人的编号,记在二维数组的元素path[j][k]中。那么方案dp(j, k)的倒数第二个人选的编号,就是path[j-1][k-V[path[j][k]]]。假定最后算出了解方案的辩控差是k,那么从path[m][k]出发,就能顺藤摸瓜一步步回溯求出所有被选中的候选人。
初始条件,只能确定dp(0, 0) = 0,其他均为-1。由此出发,一步步自底向上递推,就能求出所有的可行方案dp(m, k)( -20×m ≤ k ≤ 20×m)。实际解题的时候,会用一个二维数组dp 来存放dp(j, k)的值。而且,由于题目中辩控差的值k 可以为负数,而程序中数租下标不能为负数,所以,在程序中不妨将辩控差的值都加上修正值fix=400,以免下标为负数导致出错。
为什么fix=400?这是很显然的,m上限为20人,当20人的d均为0,p均为20时,会出现辨控差为-400。修正后回避下标负数问题,区间整体平移,从[-400,400]映射到[0,800]。
此时初始条件修正为dp(0, fix) = 0,其他均为-1。
DP后,从第m行的dp(m, fix)开始往两边搜索最小|D-P| 即可,第一个不为dp[m][k]!=-1的位置k就是最小|D-P|的所在。
最后就是求m个人的D和P,由于D+P = dp(m, |D-P| ) ,|D-P|已知。
那么D= (D+P + |D-P| )/2 , P=(D+P-|D-P| ) / 2
计算D和P时注意修正值fix

AC代码:

//Memory Time 
//388K   16MS #include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;int n;  //候选人数
int m;  //当选人数
int dp[21][801];   //dp[j][k]:取j个候选人,使其辩控差为k的所有方案中,辩控和最大的方案的辩控和
int path[21][801];  //记录所选定的候选人的编号/*回溯,确认dp[j][k]方案是否曾选择过候选人i*/
bool select(int j,int k,int i,int* v)
{while(j>0 && path[j][k]!=i){k-=v[ path[j][k] ];j--;}return j?false:true;
}int main(void)
{int time=1;while(cin>>n>>m && n){/*Initial*/int j,k,i;int* p=new int[n+1];  //每个人的控方值int* d=new int[n+1];  //每个人的辩方值int* s=new int[n+1];  //每个人的辨控和int* v=new int[n+1];  //每个人的辨控差memset(dp,-1,sizeof(dp));memset(path,0,sizeof(path));/*Input*/for(i=1;i<=n;i++){cin>>p[i]>>d[i];s[i]=p[i]+d[i];v[i]=p[i]-d[i];}int fix=m*20;  //总修正值,修正极限为从[-400,400]映射到[0,800]/*DP*/dp[0][fix]=0;   //由于修正了数值,因此dp[0][fix]才是真正的dp[0][0]for(j=1;j<=m;j++)for(k=0;k<=2*fix;k++){if(dp[j-1][k]>=0)   //区间已平移,dp[0][fix]才是真正的dp[0][0]{for(i=1;i<=n;i++)if(dp[j][ k+v[i] ] < dp[j-1][k]+s[i]){if(select(j-1,k,i,v)){dp[j][ k+v[i] ] = dp[j-1][k]+s[i];path[j][ k+v[i] ] = i;}}}}/*Output*/for(k=0;k<=fix;k++)if(dp[m][fix-k]>=0 || dp[m][fix+k]>=0)    //从中间向两边搜索最小辨控差的位置kbreak;int div=dp[m][fix-k] > dp[m][fix+k] ? (fix-k):(fix+k);  //最小辨控差cout<<"Jury #"<<time++<<endl;cout<<"Best jury has value ";//辩方总值 = (辨控和+辨控差+修正值)/2cout<<(dp[m][div]+div-fix)/2<<" for prosecution and value ";//控方总值 = (辨控和-辨控差+修正值)/2cout<<(dp[m][div]-div+fix)/2<<" for defence:"<<endl;int* id=new int[m];for(i=0,j=m,k=div;i<m;i++){id[i]=path[j][k];k-=v[ id[i] ];j--;}sort(id,id+m);   //升序输出候选人编号for(i=0;i<m;i++)cout<<' '<<id[i];cout<<endl<<endl;/*Relax*/delete p;delete d;delete s;delete v;delete id;}return 0;
}

这篇关于(POJ 1015) Jury Compromise 经典dp问题 (n选m)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/679526

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

hdu 2602 and poj 3624(01背包)

01背包的模板题。 hdu2602代码: #include<stdio.h>#include<string.h>const int MaxN = 1001;int max(int a, int b){return a > b ? a : b;}int w[MaxN];int v[MaxN];int dp[MaxN];int main(){int T;int N, V;s

poj 1511 Invitation Cards(spfa最短路)

题意是给你点与点之间的距离,求来回到点1的最短路中的边权和。 因为边很大,不能用原来的dijkstra什么的,所以用spfa来做。并且注意要用long long int 来存储。 稍微改了一下学长的模板。 stack stl 实现代码: #include<stdio.h>#include<stack>using namespace std;const int M