《算法竞赛进阶指南》0x52背包DP 陪审团(背包DP扩展)

题目链接:https://www.acwing.com/problem/content/description/282/

题目给出两个序列p,d 要求寻找一个方案,使得p-d的绝对值最小,在绝对值相同的情况下p+d的值尽量大,通过dp保存状态:前i个人中选择了j个,并且差为k时的p+d的最大值。转移方式就是划分集合,判断是否选择第i个人,最后通过回溯即可判断是否选择一个人。这里的差值在[-400,400]之间,所以可以加上偏移400使得下标为正。最初的状态只有f[0,0,400]=0,表示没开始选择的时候,差值为0的最大值为0。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 210,maxm = 810,base=400;
int n,m;
int p[maxn],d[maxn];
int f[maxn][21][maxm];
int ans[maxn];
int main(){
    int T=1;
    while(scanf("%d%d",&n,&m),n||m){
        for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&d[i]);
        
        memset(f,-0x3f,sizeof f);
        f[0][0][base]=0;//起初只有一个合法状态
        
        for(int i=1;i<=n;i++)
            for(int j=0;j<=m;j++)
                for(int k=0;k<maxm;k++){
                    f[i][j][k]=f[i-1][j][k];
                    int t=k-(p[i]-d[i]);
                    if(t<0 || t>=maxm)continue;
                    if(j<1)continue;
                    f[i][j][k]=max(f[i][j][k],f[i-1][j-1][t]+p[i]+d[i]);
            }
            
        int v=0;//判断差值最小的方案 
        while(f[n][m][base-v]<0 && f[n][m][base+v]<0)v++;
        
        if(f[n][m][base-v]>f[n][m][base+v])v=base-v;
        else v=base+v;
        
        int cnt = 0;
        int i=n,j=m,k=v;
        while(j){//状态回溯 
            if(f[i][j][k]==f[i-1][j][k])i--;
            else{
                ans[cnt++]=i;
                k-=(p[i]-d[i]);
                i--,j--;
            }
        }
        
        int sp=0,sd=0;
        for(int i=0;i<cnt;i++)
            sp+=p[ans[i]],sd+=d[ans[i]];
        printf("Jury #%d\n",T++);
        printf("Best jury has value %d for prosecution and value %d for defence:\n",sp,sd);
        for(int i=0;i<cnt;i++)printf(" %d",ans[i]);
        cout<<endl<<endl;
    }
    return 0;
}

 

posted @ 2020-07-30 09:47  WA自动机~  阅读(169)  评论(0编辑  收藏  举报