dp训练计划——poj1015(01背包变形+路径输出)

题目链接:https://vjudge.net/problem/POJ-1015

题目大意:

在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人,然后再从这n个人中选m人组成陪审团。选m人的办法是: 控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0到20。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。

Input

输入包含多组数据。每组数据的第一行是两个整数n和m,n是候选人数目,m是陪审团人数。注意,1<=n<=200, 1<=m<=20 而且 m<=n。接下来的n行,每行表示一个候选人的信息,它包含2个整数,先后是控方和辩方对该候选人的打分。候选人按出现的先后从1开始编号。两组有效数据之间以空行分隔。最后一组数据n=m=0

Output

对每组数据,先输出一行,表示答案所属的组号,如 'Jury #1', 'Jury #2', 等。接下来的一行要象例子那样输出陪审团的控方总分和辩方总分。再下来一行要以升序输出陪审团里每个成员的编号,两个成员编号之间用空格分隔。每组输出数据须以一个空行结束。

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 allowed time.

题解:

一道很不错的dp,需要进行一些巧妙的转换。

首先我们把每个人看成容量,那么最大容量V就变成了m,相当于把n个物品放入容量为m的背包里面,而且每个物品的容量为1,之后我们不难想出p-v就是每个物品的费用。最后考虑滚动数组优化,我们逆序枚举容量(即选i个人),可以费用的范围是[-400,400],比较小,那么我们就可以考虑枚举所有的费用情况,同时维护一下保证d+p最大就行了。

 状态:dp[i][j]表示选i个人(即容量),辩控差(即费用)为j的所有方案中最大辩控和的方案。

状态转移方程:dp[i+1][j+sub[k]]=max(dp[i+1][j+sub[k]],dp[i][[j]+add[k])

其实就是选i+1个人,辩控差为j+sub[k]的最优方案是由所有能够通过选i个人,辩控差为j再加上一个人k的贡献达到这个状态的辩控和最大的那个方案。

因为这个费用可能为负,所以我们要考虑把[-400,400]映射到[0,800],相当于加了一个修正值fix=20*m.

中间计量记录答案的时候注意记录一下路径就行了,至于求被选择的最优解的d+p总和以及d-p总和,我的方法是枚举路径中的点,记录答案就行了,有一个简单的公式可以记录答案,不过不是特别理解,就有简单的方法直接求的。

代码实现:

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define debug printf("ac\n");
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int INF = 0x3f3f3f3f;
const int N = 1e3+7;
int n,m;
int sub[205],add[205],d[205],p[205];
int dp[25][805];
vector<int> path[25][805];
void init(){
    rp(i,0,m-1) rp(j,0,801) path[i][j].clear();
    mst(dp,-1);
}
int main(){
    int kcase=0;
    while(~scanf("%d%d",&n,&m)&&n&&m){
        rp(i,0,n-1){
            d[i]=read(),p[i]=read();
            sub[i]=d[i]-p[i];
            add[i]=d[i]+p[i];
        }
        init();
        int fix=m*20;
        dp[0][fix]=0;
        rp(k,0,n-1){
            RP(i,m-1,0){
                rp(j,0,2*fix-1){
                    if(dp[i][j]>=0){
                        if(dp[i+1][j+sub[k]]<=dp[i][j]+add[k]){
                            dp[i+1][j+sub[k]]=dp[i][j]+add[k];
                            path[i+1][j+sub[k]]=path[i][j];
                            path[i+1][j+sub[k]].pb(k);
                        }
                    }
                }
            }
        }
        int i;
        for(i=0;dp[m][fix+i]==-1&&dp[m][fix-i]==-1;i++);
        //这里可以保证辩控差是最小的,不难理解
        int temp=dp[m][fix+i]>dp[m][fix-i]?i:-i;//记录最优解的状态,同时保证d+p的和最大
        printf("Jury #%d\n",++kcase);
        int sumD=0,sumP=0;
        rp(i,0,m-1){
            sumD+=d[path[m][fix+temp][i]];
            sumP+=p[path[m][fix+temp][i]];
        }
        printf("Best jury has value %d for prosecution and value %d for defence:\n",sumD,sumP);
        rp(i,0,m-1) printf(" %d",path[m][fix+temp][i]+1);
        printf("\n\n");
    }
    return 0;
}
/*
1 7
3
3 3
6 6
2 2 
*/  
发布了342 篇原创文章 · 获赞 220 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43472263/article/details/104615640
今日推荐