首页 > 代码库 > dp1015
dp1015
题目大意: 在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分和控方总分的差的绝对值最小(这M个人加起来)。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大(这M个人加起来)的方案即可。最终选出的方案称为陪审团方案。(d[k]为控方对每个人的评分,p[k]为辩方对每个人的评分)
我觉得最难的地方在于用二维数组抽象问题,来表示问题的最优解。
这种问题中,包含了两个因素来决定最优解:差最小、和最大,因此用二维数组表示最合适。同时递归最小解过程中,个数必须考虑(以此递归),因而个数也要加上去。
此题与背包问题相似的地方在于都是对于拿取的顺序无要求,所以取了第I个后,遍历所有第I个的所有状态【J】,得到第I+1的结果。
与背包不同的地方在于这里对于个数是确定的,并不是可以取任意多个
for (int i=0; i<=m-1; i++)这层是确定个数
for (int j=0; j<=800; j++)这层是对于某个I遍历所有状态
for (int l=1; l<=n && dp[i][j]>=0; l++)这层循环就是为了防止重复
并且,我们还规定,如果没法选j 个人,使其辩控差为k,那么dp(j, k)的值就为-1,也称方案dp(j, k)不可行
为什么fix=400?这是很显然的,m上限为20人,当20人的d均为0,p均为20时,会出现辨控差为-400。修正后回避下标负数问题,区间整体平移,从[-400,400]映射到[0,800]。
此时初始条件修正为dp(0, fix) = 0,其他均为-1。
if (dp[i+1][j+num[l].d-num[l].p]<dp[i][j]+num[l].d+num[l].p)
替换原先DP[I+1][J]中,总分之和比较小的(小于第I+1个元素 加进来之后的总分和)
83 while (dp[m][i+k]<0 && dp[m][i-k]<0) k++; //求出最小的差值
实际是应该是求D分数和与P分数和之差的绝对值,但前面统一用D-P求值,因此只需要在最后考虑加绝对值。即根据题意,差值应该是个正值(取绝对值),但因为一直用D-P,所以可能变成负的,因此可能最终的结果在400+k,中,也可能在400-k中,需要比较。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <algorithm>
using namespace std;
struct player
{
int d,p;
}num[300]; //表示控方和辩方分别给的分数
int result[300]; //储存最终的m个陪审团成员
int n,m,x,y,xx,yy;
int path[30][1000]; //记录选择i个人,以差值j结尾的陪审团成员的最后一个人
int dp[30][1000]; //记录选择i个人,差值为j的最大和是多少
void init()
{
memset(path,0,sizeof(path));
memset(result,0,sizeof(result));
for (int i=0; i<=m; i++)
for (int j=0; j<=800; j++)
{
dp[i][j]=-1;
}
dp[0][400]=0; //什么都没有的时候,即为0;400是贯穿整个程序的,目的是为了使差值由负变正,可以在数组在中储存
}
void get_dp()
{
for (int i=0; i<=m-1; i++)
{
for (int j=0; j<=800; j++)
{
for (int l=1; l<=n && dp[i][j]>=0; l++)
{
if (dp[i+1][j+num[l].d-num[l].p]<dp[i][j]+num[l].d+num[l].p)//左边是在dp[i][j]基础上不加i+1的情况,后者是加了后的情况
{
x=i;
y=j;
while(x>0&&path[x][y]!=l)//不是x>=0
{
y-=num[path[x][y]].d-num[path[x][y]].p; //判断这个新加入的l在原来的成立的陪审团中是否存在
x--;
}
if(x==0)
{
dp[i+1][j+num[l].d-num[l].p]=dp[i][j]+num[l].d+num[l].p;
path[i+1][j+num[l].d-num[l].p]=l;
}
}
}
}
}
}
int cmp(int a,int b)
{
return a<b;
}
int main()
{
int T=0,i;
FILE *fp=fopen("1.txt","r");
while(fscanf(fp,"%d%d",&n,&m))
// while (~scanf("%d%d",&n,&m))
{
T++;
if (n==0 && m==0) break;
init(); //初始化
int xx,yy;
for ( i=1; i<=n; i++)
{
fscanf(fp,"%d%d",&xx,&yy);
num[i].d=xx;
num[i].p=yy;
}
get_dp(); //dp[i][j]表示的是选择i个人,差值为j的最大和是多少
int k=0;
int i=400; //400是贯穿整个程序的,目的是为了使差值由负变正,可以在数组在中储存
int temp;
while (dp[m][i+k]<0 && dp[m][i-k]<0) k++; //求出最小的差值
//找出m个人的陪审团成员,和统计m个人的陪审团控方得分和和辩方得分和
if (dp[m][i+k]>dp[m][i-k]) temp=i+k;
else temp=i-k;
int x=y=0;
for( i=0; i<=m-1; i++)
{
result[i]=path[m-i][temp];
x+=num[result[i]].d;
y+=num[result[i]].p;
temp-=num[result[i]].d-num[result[i]].p;
}
//题目要求从小到大输出~第一次就WA在这里,注意审题
sort(result,result+m,cmp);
cout << "Jury #" << T << endl;
cout << "Best jury has value " << x << " for prosecution and value " << y << " for defence:" << endl;
for ( i=0; i<=m-1; i++)
cout << " " << result[i];
cout << endl;
}
return 0;
}