DP::Poj1015 July Compromise

 

 

初学DP,夜鱼就给我找了这么条难缠的“大鱼”,有点打击到我~~~~

 问题:n個候選人,要挑m個組成陪審團。選人方法:控方和辯方根據喜歡程度為每個候選人打分(0~20),選出的這m個人,必須滿足辯方總分和控方總分的差的絕對值最小。如有多種方案,則選辯控雙方總分之和最大的方案即可。

輸入:1<=n<=200, 1<=m<=20, m<=n; pi,di(i=1,…,n)表控、辯打分數。

解决DP问题的关键在于列出递推方程,除此之外还要证明两点:最优子结构和重叠子问题。关于前者,采用的是“剪贴”技术,本质是反证法,本题的子问题很明显,当考虑i个人时,那么可以从i-1个人的最优方案中获得解的部分。关于后者,本题子问题的规模是越来越小的,直到边界i=1。列递推方程的关键在于有个好的状态描述,本题,子问题规模i个人,差要求最小,在此基础上要求和最大。最后的“要求”往往是“状态”的值,于是不难写出状态s[i,j],而根据该状态的定义,用小一级的子问题来表述之,就是下面所列的方程。s[i,j]表示i个人且差之和为j时所有方案中和之和的最大值。则s[i,j]=max{s[i-1,j-(d[k]-p[k])]+(d[k]+p[k])},(k不在s[i-1,*]中)

本题在输入的时候,是借鉴PKU(1002)的Hint部分,除了下面的格式之外,还可以while(scanf("%d %d", &n, &m),n||m)或while(cin>>n>>m,n||m).

题目最终是要求输出节点的,所以可以用hash表来记录,这样做还有个好处,那就是方程的“(i不在s[i-1,*]中)”部分同时也解决了,因为对访问过且采用的节点可以做记录,从而进行判断。本题是用二维数组来实现的,记号path[][]参考了别人的。

接下来就是算法的核心了,首先是方程的初始状态,也就是边界。因为是max,所以用s[1][20m+a[i]]<b[i]来筛选,并用path作记录。有了s[1][]就可以递推了,用s[i+1][j+a[k]]<s[i][j]+b[k]来筛选。由方程可看出是要用三个for循环的,分别对应i,j,k。注意除此之外最里面的for循环只是用来处理方程的“(k不在s[i-1,*]中)”部分而已。

剩下的部分都是细枝末节了。 

 **************************************************************************

#include "stdafx.h"
#include <algorithm>
#include <iostream>
using namespace std;

int main()
{
 int s[21][805],path[21][805],a[205],b[205],c[21];
 int p,d,n,m,min,cn=0,minus;
//输入
 while(scanf("%d%d", &n, &m)&&n+m){
      for(int i = 1; i <= n; ++i){
           scanf("%d%d", &p, &d);
           b[i] = p+d;
           a[i] = p-d;   
      }
      minus=20*m;
//初始化
      memset(s, -1, sizeof(s));
      memset(path, -1, sizeof(path));
      s[0][minus] = path[0][minus] = 0;
//边界
      for(int i = 1; i <= n; ++i){
          if(s[1][minus+a[i]] < b[i]){
              path[1][minus+a[i]] = i;
              s[1][minus+a[i]] = b[i];
          }
      }
//根据状态方程DP
      int ii,jj;
      for(int i = 1; i < m; ++i){
          for(int j = 0; j < 40*m; ++j){
              if(path[i][j] >= 0){
                  for(int k = 1; k <= n; ++k){
                      if(s[i+1][j+a[k]] < s[i][j]+b[k]){
                          for(jj = j, ii = i; ii >= 1; --ii){
                              if(path[ii][jj] == k) break;
                              jj -= a[path[ii][jj]];
                          }
                          if(ii < 1){
                              path[i+1][j+a[k]] = k;
                              s[i+1][j+a[k]] = s[i][j]+b[k];
                          }
                      }
                  }
              }
          }
      }
//计算最小值min
     for(int j = 0; j <= 40*m; ++j){
         if(s[m][minus+j] >= 0 || s[m][minus-j] >= 0){
             if(s[m][minus+j] > s[m][minus-j])
                  min = minus+j;
             else
                  min = minus-j;
             break;
         }
    }
//沿着路径找节点
    int count=0;
    for(int j=m,k=min; j >= 1; --j){
        c[count++] = path[j][k];
        k -= a[c[count-1]];
    }
    sort(c, c+count);
//打印
    printf("Jury #%d\n", ++cn);
    printf("Best jury has value %d for prosecution and value %d for defence:\n", (s[m][min]+min-minus)/2, (s[m][min]-min+minus)/2);
    for(int i = 0; i < count; ++i)
        printf(" %d",c[i]);
    printf("\n");
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值