(常複習)poj 1015 dp+記錄dp路徑+轉變最優子結構+區間映射

Jury Compromise
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 24812   Accepted: 6464   Special Judge

Description

In Frobnia, a far-away country, the verdicts in court trials are determined by a jury consisting of members of the general public. Every time a trial is set to begin, a jury has to be selected, which is done as follows. First, several people are drawn randomly from the public. For each person in this pool, defence and prosecution assign a grade from 0 to 20 indicating their preference for this person. 0 means total dislike, 20 on the other hand means that this person is considered ideally suited for the jury. 
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

The input file contains several jury selection rounds. Each round starts with a line containing two integers n and m. n is the number of candidates and m the number of jury members. 
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

For each round output a line containing the number of the jury selection round ('Jury #1', 'Jury #2', etc.). 
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

If your solution is based on an inefficient algorithm, it may not execute in the allotted time.

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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章