Time Limit: 1000MS | Memory Limit: 65536K | |||
Total Submissions: 24812 | Accepted: 6464 | Special Judge |
Description
Based on the grades of the two parties, the judge selects the jury. In order to ensure a fair trial, the tendencies of the jury to favour either defence or prosecution should be as balanced as possible. The jury therefore has to be chosen in a way that is satisfactory to both parties.
We will now make this more precise: given a pool of n potential jurors and two values di (the defence's value) and pi (the prosecution's value) for each potential juror i, you are to select a jury of m persons. If J is a subset of {1,..., n} with m elements, then D(J ) = sum(dk) k belong to J
and P(J) = sum(pk) k belong to J are the total values of this jury for defence and prosecution.
For an optimal jury J , the value |D(J) - P(J)| must be minimal. If there are several jurys with minimal |D(J) - P(J)|, one which maximizes D(J) + P(J) should be selected since the jury should be as ideal as possible for both parties.
You are to write a program that implements this jury selection process and chooses an optimal jury given a set of candidates.
Input
These values will satisfy 1<=n<=200, 1<=m<=20 and of course m<=n. The following n lines contain the two integers pi and di for i = 1,...,n. A blank line separates each round from the next.
The file ends with a round that has n = m = 0.
Output
On the next line print the values D(J ) and P (J ) of your jury as shown below and on another line print the numbers of the m chosen candidates in ascending order. Output a blank before each individual candidate number.
Output an empty line after each test case.
Sample Input
4 2 1 2 2 3 4 1 6 2 0 0
Sample Output
Jury #1 Best jury has value 6 for prosecution and value 4 for defence: 2 3
Hint
Source
題目大意:
n個候選人,從裏面選出m個來組成陪審團,
每個候選人有兩個屬性,一個是“控方”對其的偏好p[i],另一個是“反方”對其的偏好d[i]
要求輸出使得|sigma(p[i]) - sigma(p[i])| 最小,如果存在多個相同答案則求sigma(p[i]) + sigma(p[i])最大的一個
的sigma(p[i]) 值和 sigma(p[i])值,以及所選的m個人。
反省&總結:
(1)這道題一開始先是因爲dp循環的第二層j的部分寫成了j<=i而不是i<=m導致越界RE,查了很久查不出來,因爲寫得時候
注意力沒有時時集中,思路不夠嚴謹,沒有考慮好每一步這麼寫是否正確。
(2)接着就是WA了,其實也好解決,也是粗心,對着測試數據發現是最後輸出sigma(p[i])和sigma(p[i])的兩個printf裏面的參數是一樣的= =
因爲當初從一個複製到另一個的時候沒有記得馬上改好
接下來說說思路吧:
這道題是第一道非水的,基本上憑藉自己的思考推倒出dp公式的,dp題。
先說轉移方程
dp[i][j][k]表示,選到第i個候選人,已經選好了m個陪審團員當前|sigma(p[i]) - sigma(p[i])| 的值爲k,時,sigma(p[i]) + sigma(p[i]) 的值
那麼
dp[i][j][k] = max ( dp[i-1] [j] [k] , dp[i-1] [j-1] [ k-( p[i] - d[i] ) ] );
(1)最優子結構
首先第一眼看到的時候就覺得這尼瑪不是揹包麼。但是細細看來不對,他的第一個目標函數|sigma(p[i]) - sigma(p[i])| 並不滿足
最優子結構,也就是雖然說當前|sigma(p[i]) - sigma(p[i])| 已經是最小,但是你無法由當前的最小得到下一狀態的最小。
而在dp過程中是必須保證目標函數(這裏特指dp本身的那個值)是最優子結構的。
那麼怎麼保證dp具有最優子結構呢?
觀察發現第二個指標是可以實現最優子結構的(求個最大值)
那麼我們就不用第一個指標,而用第二個作爲目標函數
而對於第一個指標,我們需要把所有的可能都記錄下來,直接遍歷出第一個合法的答案
這樣我們就要多開一維數組,第三維的k用來專門記錄第一個指標的值。
最後要求解最終答案時,我們就從k=400開始往上下找(400加減),最先找到的,dp.sum不爲-1(已經處理過的有效的)的那個dp就是答案。
(2)區間映射
接着還會碰到一個問題,上面的|sigma(p[i]) - sigma(p[i])| 是絕對值,按照上面的處理是要區分正負的。
這個時候就需要重新設置基準點,因爲最多選二十個人,每個人最多是20分,那麼最多是20*20=400
所以我們重新設置基準點,也就是0點爲400.
(3)路徑還原
這道題比較蛋疼的還有,他還讓輸出所選的候選人,我的處理方式是用一個結構來dp,在結構中存儲轉移的當前狀態的上一個狀態的
後兩個下標,這樣就可以不斷向前索引,還原出路徑了。
下面是ac代碼:
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <numeric>
#include <functional>
#define maxn 500005
using namespace std;
struct node{
int sum; //記錄D和P的和
int j,k; //回溯索引用
bool flag; //dp[i][j][k] 表示前一個人被選上了沒
node(){
sum=-1;
j=-1,k=-1;
flag=false;
}
};
int n,m,range;
node dp[202][21][850];
int p[205],d[205];
void init()
{
for(int i=0;i<=n;i+=1){
for(int j=0;j<=m;j+=1){
for(int k=400-range;k<=400+range;k+=1){
dp[i][j][k].sum=-1;
dp[i][j][k].j=-1;
dp[i][j][k].k=-1;
dp[i][j][k].flag=false;
}
}
}
for(int i=0;i<=n;i+=1){
dp[i][0][400].sum=0;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int cas=1;
while(1){
init();
scanf("%d%d",&n,&m);
range=20*m;
if(!n&&!m) break;
for(int i=1;i<=n;i+=1){
scanf("%d%d",&d[i],&p[i]);
}
for(int i=1;i<=n;i+=1){
for(int j=1;j<=m/*喝喝j<=i*/;j+=1){
if(j==1){
dp[i][j][400+(p[i]-d[i])].sum=p[i]+d[i];
dp[i][j][400+(p[i]-d[i])].flag=true;
dp[i][j][400+(p[i]-d[i])].j=j-1,dp[i][j][400+(p[i]-d[i])].k=400;
for(int k=400-range;k<=400+range;k+=1){
if(dp[i-1][j][k].sum!=-1){
if(dp[i-1][j][k].sum>dp[i][j][k].sum){
dp[i][j][k].sum=dp[i-1][j][k].sum;
dp[i][j][k].j=j,dp[i][j][k].k=k;
dp[i][j][k].flag=false;
}
}
}
continue;
}
for(int k=400-range;k<=400+range;k+=1){
if((k-(p[i]-d[i])<400-range||k-(p[i]-d[i])>400+range)){
if(dp[i-1][j][k].sum!=-1){
//沒選當前這個人
dp[i][j][k].sum=dp[i-1][j][k].sum;
dp[i][j][k].j=j,dp[i][j][k].k=k;
}
continue;
}
if(dp[i-1][j][k].sum==-1&&dp[i-1][j-1][k-(p[i]-d[i])].sum==-1) continue;
if(dp[i-1][j][k].sum==-1&&dp[i-1][j-1][k-(p[i]-d[i])].sum!=-1){
//選了當前這個人
dp[i][j][k].sum=dp[i-1][j-1][k-(p[i]-d[i])].sum+d[i]+p[i];
dp[i][j][k].j=j-1;
dp[i][j][k].k=k-(p[i]-d[i]);
dp[i][j][k].flag=true; //當前這人被選了
}
else if(dp[i-1][j][k].sum!=-1&&dp[i-1][j-1][k-(p[i]-d[i])].sum==-1){
//沒選當前這個人
dp[i][j][k].sum=dp[i-1][j][k].sum;
dp[i][j][k].j=j,dp[i][j][k].k=k;
}
else if(dp[i-1][j][k].sum>dp[i-1][j-1][k-(p[i]-d[i])].sum+d[i]+p[i]){
//沒選當前這個人
dp[i][j][k].sum=dp[i-1][j][k].sum;
dp[i][j][k].j=j,dp[i][j][k].k=k;
}
else{
//選了當前這個人
dp[i][j][k].sum=dp[i-1][j-1][k-(p[i]-d[i])].sum+d[i]+p[i];
dp[i][j][k].j=j-1,dp[i][j][k].k=k-(p[i]-d[i]);
dp[i][j][k].flag=true; //當前這人被選了
}
}
}
}
int ansi=0;
printf("Jury #%d\n",cas++);
for(int i=0;i<=range;i+=1){
if(dp[n][m][400+i].sum==-1&&dp[n][m][400-i].sum==-1) continue;
else{
if(dp[n][m][400+i].sum>dp[n][m][400-i].sum){
printf("Best jury has value %d for prosecution and value %d for defence:\n"
,(-i+dp[n][m][400+i].sum)/2,(dp[n][m][400+i].sum+i)/2);
/*喝喝(-i+dp[n][m][400-i].sum)/2,(dp[n][m][400-i].sum+i)/2*/
ansi=400+i;
}
else{
printf("Best jury has value %d for prosecution and value %d for defence:\n"
,(i+dp[n][m][400-i].sum)/2,(dp[n][m][400-i].sum-i)/2);
ansi=400-i;
}
break;
}
}
int j=m,k=ansi;
ansi=0;
for(int i=n;i>=1;i-=1){
int tj=j,tk=k;
if(dp[i][tj][tk].flag){
p[ansi++]=i;
}
j=dp[i][tj][tk].j;
k=dp[i][tj][tk].k;
// printf("(%d,%d)\n",j,k);
}
for(int i=m-1;i>=0;i-=1){
printf("%d",p[i]);
if(i!=0) printf(" ");
}
printf("\n");
}
return 0;
}