(常复习)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;
}



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