poj 1015 dp

1 篇文章 0 订阅

题目大意:

给定两组数,每组n个,p1...pn,d1..dn,其中pi,di对应于同一个人,要选出m个人,这m个人的Σp - Σd的绝对值最小,当有多组数据符合条件时,选择Σp+Σd的绝对值最大的一组数

从小到大print选择的m个人的编号


思路分析:

这道题的难点在于确定状态转移方程的参量含义,参考了一下网上的解法,

dp[i][j]表示已经选了i个人且 Σp - Σd = j 时 Σp+Σd的值

状态转移方程是

if(dp[i][j] + p[k] + d[k] > dp[i+1][j +p[k] - d[k]  && k 不在dp[i][j]已经选过的人中){

    dp[i+1][j+p[k] - d[k]] = dp[i][j] + p[k] + d[k];

}

那么怎样判断k是不是在达到dp[i][j]状态时所选择的人中呢?

可以相应的用path[i][j]表示达到dp[i][j]时选择的人,这样逐步回溯就可以找到所有参与形成dp[i][j]的人


解题技巧:

1.由于p[k]-d[k]可能为负数,所以要对结果做整体的偏移, 题目中已经给定1<=m<=20且pk,dk的值都在0-20之间,所以可以

令偏移量为400,避免j出现负数

2.用path[i][j]回溯的方法记录所选择的人,这种方法在floyd算法求任意两点间最短路的时候也有用到

3.从400(偏移量)向两边探测出使得dp[m][k] !=0的值


#include<iostream>
#include<cstdio>
#include<string.h>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;

const int maxn = 205;
const int maxm = 25;
const int offset = 400;

int dp[maxm][(offset<<1)+50];
int path[maxm][(offset<<1)+50];
int n,m;
int d[maxn],p[maxn]; //记录信息
int main(){
/*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 */
	//freopen("1.txt","r",stdin);
	int i,j,k,Ca = 1;
	while(scanf("%d%d",&n,&m)!=EOF){
		if(n == 0 && m == 0)
			break;
		for(i = 0; i < n; ++i){
			scanf("%d%d",p+i,d+i);
		}

		/*init*/
		memset(dp,-1,sizeof(dp));
		memset(path,0,sizeof(path));
		int N = m * 20;
		dp[0][N] = 0;
		/*init*/
		//dp[i][j]表示一共有i个人且Σp - Σq = j时的Σp+Σq

		int twiceN = N << 1,t1,t2,t3;
		for(i = 0; i < m; ++i){
			for(j = 1; j <= twiceN; ++j){
				if(dp[i][j] >= 0){
					for(k = 0; k < n; ++k){
						if(dp[i][j] + p[k] + d[k] > dp[i+1][j + p[k] - d[k]]){
							t1 = i;  t2 = j;
							while(t1 > 0 && path[t1][t2] != k){
								t3 = path[t1][t2];
								t2 -= p[t3] - d[t3];
								--t1;
							}
							if(t1 == 0){
								dp[i+1][j + p[k] - d[k]] = dp[i][j] + p[k] + d[k];
								path[i+1][j+p[k]-d[k]] = k;
							}
						}
					}
				}
			}
		}

		int ty = 0;
		while(dp[m][N-ty] < 0 && dp[m][N+ty] < 0)
			++ty;
		int record;
		if(dp[m][N-ty] > dp[m][N+ty]){
			record = N - ty;
		}
		else{
			record = N + ty;
		}

		int pans = (dp[m][record] + record - N) >> 1;
		int dans = (dp[m][record] - record + N) >> 1;

		printf("Jury #%d\n",Ca++);
		printf("Best jury has value %d for prosecution and value %d for defence:\n",pans,dans);
	
		int ans[maxm];
		int peek = 0;
		/*for(j = path[m][record]; j != 0;){
			ans[peek] = j;
			j = path[--m][record - p[j] + d[j]];
		}*/
		/*the above part is incorrect too*/
		for(i = 0; i < m; ++i){
			ans[peek] = path[m-i][record];
			record -= p[ans[peek]] - d[ans[peek]];
			++peek;
		}
		sort(ans,ans+m);

		for(j = 0;j < m; ++j){
			printf(" %d",ans[j]+1);
		}
		printf("\n\n");
	}


	return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值