poj 1015 Jury Compromise 状态压缩DP(不压缩也行)

Jury Compromise
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 24261 Accepted: 6313 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

==============================================================================

题目大意:说有个地方给法庭安排陪审团人员有特别的规则。N个候选人里挑M个当陪审团成员,N个人每人都对被告打两个分,P和D,范围都是[ 0 , 20 ]。P分越高就表示越愿意惩罚被告,D分越高就表示越愿意维护被告。力求尽量使陪审团中立,所以要求最后∑P与∑D差尽量小,如果∑P=∑D,则选∑P+∑D更大的方案。输出∑P、∑D和具体方案。


解题思路:

用搜索遍历所有组合可能求解吗?别天真了C(20,200)是个吓死人的大数。

①当已经选了m个候选人,要选第m+1个人时应该从没被选的人中选择使 |∑P-∑D| 最小同时使 ∑P+∑D 尽量大的人。

②每选过一个人,每种选人方案都有一组新的 ∑P-∑D 和 ∑P+∑D 值。

③不论已经选了几个人,选新的人后更优的方案(参考①)有权取代更次的方案对 ∑P-∑D 值的占用。

这样一来,选择到m个人时,尚存的备选方案的数目只受 ∑P-∑D 限制,而 ∑P-∑D 的范围为 [ -20*M, 20*M ] (最小:M个人全是P=0,D=20;最大:M个人全是P=20,M=0)。题中M最大值为20,则每次新选一个人,最多有400+400+1=801个方案备选。

最后一次选人后,所有备选方案中选出 ∑P-∑D 最小且 ∑P+∑D 最大的即可。

先来看看时间复杂度,O(20*801*200)差不多O(3.2*10^6),用2.5GHz算也就不到12ms,可以接受。

再来看空间占用情况,801*20个状态,每个状态存选中的人,用bool型200字节, ∑P-∑D 和 ∑P+∑D 值,用int型8字节。加起来差不多3.2M,3000+K,题目给了60000+K,也可以接受。

当然也可以把每个方案的选人情况状态压缩一下,用二进制位来存,一个int用30位的话需要7个int,一个状态就只用不到40个字节了,总占用减到600+K,别的零头加起来不会超过1000K。这里我就压缩一下吧。

//Memory: 972K	Time: 16MS

#include<cstdio>

const int N = 200;
const int M = 20;
const int FIX = 20*M;
const int SUB = 2*FIX+1;
struct state                              //状态结构
{
    int used[7];                          //用7个int的位标记选了哪些人
    int sum, sub;                         //保存方案当前状态的∑P+∑D和∑P-∑D
    bool isUsed(int id)                   //状态结构提供方法查询某人是否被该方案选中过
    {
        return 1<<id%30 & used[id/30];
    }
    void add(state s, int id)             //状态结构提供方法以上一个状态为基础选中新的人,填写当前状态
    {
        for( int i = 0 ; i < 7 ; i++ )
            used[i] = s.used[i];
        used[id/30] |= 1<<id%30;
    }
};

state dp[M+1][SUB];                       //DP状态表
int n, m, sum[N], sub[N];                 //记录单个人的P+D和P-D,方便DP时使用
//动态规划
void DP()
{
    for( int i = 0 ; i <= m ; i++ )
        for( int j = 0 ; j < SUB ; j++ )
            dp[i][j].sum = -1;            //初始化状态表,sum=-1表示当前∑P-∑D未被某方案的状态占用
    dp[0][FIX].sum = 0;                   //定义一个起点状态,未选人
    for( int i = 1 ; i <= m ; i++ )
        for( int j = 0 ; j < SUB ; j++ )
            if(dp[i-1][j].sum > -1)       //由尚存方案的状态派生新状态
                for( int k = 0 ; k < n ; k++ )
                    if(!dp[i-1][j].isUsed(k))
                        if(dp[i][j+sub[k]].sum < dp[i-1][j].sum+sum[k])
                        {
                            dp[i][j+sub[k]].sum = dp[i-1][j].sum+sum[k];
                            dp[i][j+sub[k]].sub = j+sub[k]-FIX;
                            dp[i][j+sub[k]].add(dp[i-1][j],k);
                        }
}
//输入所有候选人的p、d值,记录p+d、p-d
void input()
{
    for( int i = 0 ; i < n ; i++ )
    {
        int p, d;
        scanf("%d%d", &p, &d);
        sum[i] = p+d;
        sub[i] = p-d;
    }
}
//DP之后在dp[m]中寻找最优解
state bestDP()
{
    bool lOK = false;
    bool rOK = false;
    int lDP, rDP;
    for( int i = 0 ; i <= FIX ; i++ )              //从0开始遍历|∑P-∑D|
    {                                              //寻找|∑P-∑D|最小的方案
        if( !lOK && dp[m][FIX-i].sum > -1 )
        {
            lOK = true;
            lDP = i;
        }
        if( !rOK && dp[m][FIX+i].sum > -1 )
        {
            rOK = true;
            rDP = i;
        }
        if( lOK || rOK )
            break;
    }
    if( lOK && rOK )                               //若有两方案|∑P-∑D|相同,选∑P+∑D大的方案
    {
        if(dp[m][FIX-lDP].sum > dp[m][FIX+rDP].sum )
            return dp[m][FIX-lDP];
        else
            return dp[m][FIX+rDP];
    }
    else if( lOK )
        return dp[m][FIX-lDP];
    else
        return dp[m][FIX+rDP];
}
//输出
void output(int &caseNum, state s)
{
    int P = (s.sum+s.sub)/2;                       //计算∑P
    int D = (s.sum-s.sub)/2;                       //计算∑D
    printf("Jury #%d \n", caseNum++);              //按要求输出
    printf("Best jury has value %d for prosecution and value %d for defence: \n",P,D);
    for( int i = 0 ; i < n ; i++ )
        if( s.isUsed(i) )
            printf(" %d", i+1);
    printf("\n\n");
}

int main()
{
    int caseNum = 1;
    while(scanf("%d%d", &n, &m),n)
    {
        input();
        DP();
        output(caseNum, bestDP());
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值