【浮*光】 #多维0/1背包# poj 1015 维护序列

【poj 1015】Jury Compromise

---------标签:多维度的0/1背包----------

  • 从n个人中选m人组成陪审团。办法是:两个人根据对它们的喜欢程度打分。
  • 分值从0到20。选出的m个人得分中,满足第一人总分D和第二人总分P的差的绝对值|D-P|最小。
  • 如果有多种选择方案的|D-P|值相同,那么选两人总分之和D+P最大的方案。
  • 输出:这m个人的第一人总值D和第二人总值P,并升序输出m人的编号。

 

思路1:f数组记录可行性。【可行性DP】

把n个候选人看成n个物品,每个物品有如下三个需考虑的因素:

1.“人数”,每人的“人数”为1,要填满容量m的背包。

2.“第一人得分”,即第一人给该候选人打的分数a[i]。

3.“第二人得分”,即第二人给该候选人打的分数b[i]。

循环考虑每个候选人的入选情况,循环到阶段i时,

表示已经考虑了前i人的入选情况,用f[j][d][p]表示已有j人入选时、方案可行性。

方程:f[j][d][p]=f[j][d][p] or f[j-1][d-a[i]][p-b[i]] ;

初值:f[0][0][0]=true; 其余均为false。

目标:某个状态f[m][d][p]=true,且|d-p|最小。|d-p|相同时d+p最大。

 

思路2:第二维度记录d-p,f数组记录d+p的最大值。

状态:循环到第i个人时,f[j][k]表示已有j人入选、且d-p=k时,d+p的最大值。

方程:f[j][k]=max{ f[j][k] , f[j-1][k-a[i]+b[i]]+a[i]+b[i] };

初值:f[0][k]=0; 其余均为负无穷。

目标:某个状态f[m][k]=true,且|k|最小。|k|相同时f[m][k]最大。

 

【注意1】

k=d-p,k可能为负数。而程序中数组下标不能为负数。

不妨在程序中将辩控差的值都加上修正值fix=20*m,回避下标负数问题。

区间整体平移,从[-20*m,20*m]映射到[0,20*m*2]。

此时初始条件修正为dp(0,20*m)=0,其他均为-1。

 

【注意2】

第一维度中,每个候选人只会被选一次,所以要用倒序循环。

本题还需要记录入选的具体方案,可以采用【记录转移路径】的方法。

即:额外建立数组d[j][k]记录状态f[j][k]的最大值是最后选了哪一人得到的。

求出最优解后,沿着d数组记录的路径进行转移:不断从f[j][k],

递归到 f[ j - 1 ][ k - a[d[j][k]] + b[d[j][k]] ] ,直到达到f[0][0]。

p.s.WA了好几次???原因不明 == 可能是数组大小问题吧???

AC代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
#include <vector> 
#include <map>
#include <iostream>
#include <stack>
#include <queue>
#include <set>
using namespace std;
typedef long long ll;

/*【poj 1015】Jury Compromise
从n个人中选m人组成陪审团。办法是:两个人根据对它们的喜欢程度打分。
分值从0到20。选出的m个人的得分中,必须满足第一人总分D和第二人总分P的差的绝对值|D-P|最小。
如果有多种选择方案的|D-P|值相同,那么选两人总分之和D+P最大的方案。
输出:这m个人的第一人总值D和第二人总值P,并升序输出m人的编号。 */

/*【多维度的0/1背包】思路1:f数组记录可行性。【可行性DP】
把n个候选人看成n个物品,每个物品有如下三个需考虑的因素:
1.“人数”,每人的“人数”为1,要填满容量m的背包。
2.“第一人得分”,即第一人给该候选人打的分数a[i]。
3.“第二人得分”,即第二人给该候选人打的分数b[i]。
循环考虑每个候选人的入选情况,循环到阶段i时,
表示已经考虑了前i人的入选情况,用f[j][d][p]表示已有j人入选时、方案可行性。
方程:f[j][d][p]=f[j][d][p] or f[j-1][d-a[i]][p-b[i]] ;
初值:f[0][0][0]=true; 其余均为false。
目标:某个状态f[m][d][p]=true,且|d-p|最小。|d-p|相同时d+p最大。 */

/*【多维度的0/1背包】思路2:第二维度记录d-p,f数组记录d+p的最大值。
状态:循环到第i个人时,f[j][k]表示已有j人入选、且d-p=k时,d+p的最大值。
方程:f[j][k]=max{ f[j][k] , f[j-1][k-a[i]+b[i]]+a[i]+b[i] };
初值:f[0][k]=0; 其余均为负无穷。
目标:某个状态f[m][k]=true,且|k|最小。|k|相同时f[m][k]最大。 */

//【注意】k=d-p,k可能为负数。而程序中数组下标不能为负数。
//不妨在程序中将辩控差的值都加上修正值fix=20*m,回避下标负数问题。
//区间整体平移,从[-20*m,20*m]映射到[0,20*m*2]。
//此时初始条件修正为dp(0,20*m)=0,其他均为-1。 

//【注意】第一维度中,每个候选人只会被选一次,所以要用倒序循环。

//本题还需要记录入选的具体方案,可以采用【记录转移路径】的方法。
//即:额外建立数组d[j][k]记录状态f[j][k]的最大值是最后选了哪一人得到的。
//求出最优解后,沿着d数组记录的路径进行转移:不断从f[j][k],
//递归到 f[ j - 1 ][ k - a[d[j][k]] + b[d[j][k]] ] ,直到达到f[0][0]。

const int maxn=811,maxm=31;
int n,m,t=0,a[maxn],b[maxn],id[maxm];
int f[maxm][maxn],d[maxm][maxn];

bool write(int x,int y,int z){ //判断路径能否到达(能否选择z)
  //(路径满足的条件:z是还没有被选择的人)
  //d[x-][y-]某一次值为z,即路径上有z,不成立。
  //这种情况下x不能减小到0,返回false。
  while(x&&d[x][y]!=z){ y-=a[d[x][y]]-b[d[x][y]]; x--; }
  return !x; //x=0时才返回true
}

int main(){
  while(scanf("%d%d",&n,&m) && n && m){ //输入0 0停止

    memset(d,0,sizeof(d));
    memset(f,-1,sizeof(f)); //初始化为负值
    //memset(f,127,sizeof(f));会得到无穷大
    f[0][m*20]=0; //初始化

    for(int i=1;i<=n;i++) 
      scanf("%d%d",&a[i],&b[i]);
    for(int i=1;i<=m;i++)
      for(int j=0;j<=m*40;j++)
        if(f[i-1][j]>=0) //之前的这个状态存在【可行性】
          for(int k=1;k<=n;k++){
            if(f[i][j+a[k]-b[k]]<f[i-1][j]+a[k]+b[k]&&write(i-1,j,k)){
                f[i][j+a[k]-b[k]]=f[i-1][j]+a[k]+b[k];
                d[i][j+a[k]-b[k]]=k;
            } //判断是否可以转移,并转移
          }
    printf("Jury #%d\n",++t); //输出每组数据的组数

    int k1=0; //↓↓↓寻找答案
    while(k1<=m*20&&f[m][m*20+k1]<0&&f[m][m*20-k1]<0) k1++;
    //从中间向两边搜索最小辨控差的位置k1
    //加上一个m*20,回避下标负数问题,此时就相当于在询问k1,-k1
    int ans=f[m][m*20+k1]>f[m][m*20-k1]?m*20+k1:m*20-k1; //询问正负
      
    printf("Best jury has value %d for prosecution and value %d for defence:\n",
              (ans+f[m][ans]-m*20)>>1,(f[m][ans]-ans+m*20)>>1); //输出
    //f[m][ans]=d+p,ans-m*20=|k1|=|d-p|,用此来求d、p
    
    for(int i=1,j=m,k=ans;i<=m;i++){
      id[i]=d[j][k]; k-=a[d[j][k]]-b[d[j][k]]; j--;
    } //id[i]用于记录方案
    
    sort(id+1,id+1+m); //按编号顺序
    for(int i=1;i<=m;i++) printf(" %d",id[i]);
    printf("\n\n");
  }
  return 0;
}

                                               ——时间划过风的轨迹,那个少年,还在等你。

猜你喜欢

转载自blog.csdn.net/flora715/article/details/82106843