POJ 1015 Jury Compromise(dp坑)
提议:在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人,然后再从这n个人中选m人组成陪审团。选m人的办法是:
控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0到20。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。
题解:开始想到的是二维01背包,因为评价差的总分值最大可能就只有[-400,400],所以我们整体加上20*m就可以直接放入dp的一维来解决
开两个数组:
path[i][j];//选i个人的评价差为j时最后一个人的编号
dp [i][j];//选i个人的评价差为j时最大评价和
然后使用三重循环:第一重是枚举选择1到m个人,第二重是枚举参选的人,第三重是枚举评价差的总可能值,枚举这个人去参选每种评价差的情况
然后判断当前这个人在这个位置时是否已经被选过了与现在计算的评价和是否大于存储的(因为评价差固定了)评价和
接着就是注意初始化要在加20*m的位置初始化(关键),而不是(0,0)
但是这样有bug,例如:选择135与选择246的评价差与评价和是一样的,我们可能选择了246把135忽略掉了,但是正确答案却是1356
因此还有这个办法(网上看到的):我们把循环的位置换一下,将第二重循环放在第一重,原来第一重放在第二重,其他的不变
这样只有固定每个参选人后才可以枚举下一个参选人,就解决这个bug了
接下来如果把第二重循环倒叙就可以保证不会出现重复的人,这样就不用判断这个人是否之前已经被选择过(类似01背包)
最后要注意循环顺序改变后不能回溯找路了,因为第一重循环会把路径打乱,所以我们得存储路径
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<vector> #include<string> #include<cstdio> #include<cstring> #include<iomanip> #include<stdlib.h> #include<iostream> #include<algorithm> using namespace std; #define eps 1E-8 /*注意可能会有输出-0.000*/ #define sgn(x) (x<-eps? -1 :x<eps? 0:1)//x为两个浮点数差的比较,注意返回整型 #define cvs(x) (x > 0.0 ? x+eps : x-eps)//浮点数转化 #define zero(x) (((x)>0?(x):-(x))<eps)//判断是否等于0 #define mul(a,b) (a<<b) #define dir(a,b) (a>>b) typedef long long ll; typedef unsigned long long ull; const int Inf=1<<28; const ll INF=1LL<<60; const double Pi=acos(-1.0); const int Mod=1e9+7; const int Max=440; struct node { int d,p; int sum,sub; }que[Max]; vector<int> path[25][Max<<1];//选i个人的评价差为j时最后一个人的编号 int dp[25][Max<<1];//选i个人的评价差为j时最大评价和 int now[25][Max<<1];//存值便于回溯 int ansp,ansd,ans[25]; int coun; void dfs(int i,int j) { coun=0; int siz=path[i][j].size(); for(int k=0;k<siz;++k) { ans[coun++]=path[i][j][k]; } return; } void Solve(int n,int m) { ansd=ansp=0; int fix=20*m;//右移fix memset(dp,-1,sizeof(dp));//不允许走的地方 for(int i=0;i<=m;++i) for(int j=0;j<2*fix;++j) path[i][j].clear(); dp[0][fix]=0;//向右移动fix保证所有为非负 for(int i=0;i<n;++i)//确定人后再确定下一人,这样避免出现当评论差与评论和都相同却不能存的情况 { for(int j=m;j>0;--j)//选择的人数,倒叙保证不重复(01背包) { for(int k=0;k<2*fix;++k)//评论差的值 { int kk=k-que[i].sub; if(kk>=0&&dp[j-1][kk]>=0&&dp[j][k]<dp[j-1][kk]+que[i].sum) { dp[j][k]=dp[j-1][kk]+que[i].sum; path[j][k]=path[j-1][kk];//因为遍历m个人的循环在内部,所以不能回溯找路径 path[j][k].push_back(i); } } } } for(int i=fix,j=fix;i>=0;--i,++j) { if(dp[m][i]>=0) { if(dp[m][j]>dp[m][i]) dfs(m,j);//找到(减去fix后的绝对值)最接近0的值 else dfs(m,i); break; } if(dp[m][j]>=0) { dfs(m,j); break; } } for(int i=0;i<m;++i) { ansp+=que[ans[i]].p; ansd+=que[ans[i]].d; } return; } int main() { int n,m; int coun=0; while(~scanf("%d %d",&n,&m)&&(n||m)) { for(int i=0; i<n; ++i) { scanf("%d %d",&que[i].d,&que[i].p); que[i].sum=que[i].d+que[i].p; que[i].sub=que[i].d-que[i].p; } Solve(n,m); printf("Jury #%d\n",++coun); printf("Best jury has value %d for prosecution and value %d for defence:\n",ansd,ansp); for(int i=0; i<m; ++i) printf(" %d",ans[i]+1); printf("\n\n"); } return 0; }