【蓝桥杯官网试题 - 算法提高 】求最大值 (dp,0-1背包)

题干:

问题描述

  给n个有序整数对ai bi,你需要选择一些整数对 使得所有你选定的数的ai+bi的和最大。并且要求你选定的数对的ai之和非负,bi之和非负。

输入格式

  输入的第一行为n,数对的个数
  以下n行每行两个整数 ai bi

输出格式

  输出你选定的数对的ai+bi之和

样例输入

5
-403 -625
-847 901
-624 -708
-293 413
886 709

样例输出

1715

数据规模和约定

  1<=n<=100
  -1000<=ai,bi<=1000

时间限制:1.0s   内存限制:256.0MB

解题报告:

不直接计算选定的数的ai+bi的和,而是转化为计算在ai的和一定的情况下尽量使选定的bi的和最大。于是变成为一个01背包问题,ai的值作为物体的重量,bi的值作为该物体的价值。首先过滤掉所有ai和bi均小于0的数对,令dp[i][j]表示前i个数对,选定的ai的和为j的情况下bi的和的最大值,将dp[i][j]初始化为-INF,再将所有已知合法情况初始化:dp[i][a[i]] = b[i],之后dp[i][j] = max(dp[i - 1][j], dp[i][j]),若j - a[i]存在,dp[i][j] = max(dp[i][j], dp[i - 1][j - a[i]] + b[i])。最后再统一加偏移量ZERO。值得注意的是,这个背包问题虽然也可以优化成一维,但是没必要,如果优化成一维,对于这题代码不会更简练反而会复杂不少,因为这题初始化的时候不能只对二维数组的第一行进行初始化,需要每一行都有一个值进行初始化,所以最好的办法就是直接开二维数组做最朴素的01背包,而且这题空间给的足够大,所以不需要担心MLE的问题。

AC代码:(空间大概78MB)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
int tot;
int a[101],b[101];
int dp[101][200000 + 5];//截止到第i个,a[i]和为j的情况下,b[i] 的最大值 
int zero = 100000;
const int INF = 0x3f3f3f3f;
int main()
{
	int n;
	cin>>n;
	for(int x,y,i = 1; i<=n; i++) {
		scanf("%d%d",&x,&y);
		if(x < 0 && y < 0) continue;
		a[++tot] = x, b[tot] = y;
	} 
	for(int i = 0; i<=tot; i++) {
		for(int j = -100000; j<=100000; j++) {
			dp[i][j+ zero] = -INF;
		}
	}
	for(int i = 1; i<=tot; i++) {
		dp[i][a[i]+ zero] = b[i];
	}
	for(int i = 2; i<=tot; i++) {
		for(int j = -100000; j<=100000; j++) {
			if(dp[i-1][j+ zero] != -INF) dp[i][j+ zero] = max(dp[i][j+zero],dp[i-1][j+ zero]);
			if(j - a[i] + zero >= 0 && j - a[i] + zero <= 200000) //这句必须加。 
				dp[i][j+ zero] = max(dp[i][j+ zero] , dp[i-1][j-a[i]+ zero] + b[i]);
		}
	}
	int ans = 0;
	for(int j = 100000; j>=0; j--) {
		if(dp[tot][j+ zero] >= 0) ans = max(ans, dp[tot][j+ zero] + j);
	}
	printf("%d\n",ans);
	return 0 ;
 }

或者这样也可以过:(空间162.9MB)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
int tot;
int a[MAX],b[MAX];
int dp[105][400000 + 5];//截止到第i个,a[i]和为j的情况下,b[i] 的最大值 
int zero = 100000;
const int INF = 0x3f3f3f3f;
int main()
{
	int n;
	cin>>n;
	for(int x,y,i = 1; i<=n; i++) {
		scanf("%d%d",&x,&y);
		if(x < 0 && y < 0) continue;
		a[++tot] = x, b[tot] = y;
	} 
	for(int i = 0; i<=tot; i++) {
		for(int j = -100000; j<=300000; j++) {//这里变了
			dp[i][j+ zero] = -INF;
		}
	}
	for(int i = 1; i<=tot; i++) {
		dp[i][a[i]+ zero] = b[i];
	}
	for(int i = 2; i<=tot; i++) {
		for(int j = -100000; j<=100000; j++) {
			if(dp[i-1][j+ zero] != -INF) dp[i][j+ zero] = max(dp[i][j+zero],dp[i-1][j+ zero]);
			if(j - a[i] + zero >= 0 )//这里变了
				dp[i][j+ zero] = max(dp[i][j+ zero] , dp[i-1][j-a[i]+ zero] + b[i]);
		}
	}
	int ans = 0;
	for(int j = 100000; j>=0; j--) {
		if(dp[tot][j+ zero] >= 0) ans = max(ans, dp[tot][j+ zero] + j);
	}
	printf("%d\n",ans);
	return 0 ;
 }

或者这样:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
int tot;
int a[MAX],b[MAX];
int dp[105][400000 + 5];//截止到第i个,a[i]和为j的情况下,b[i] 的最大值 
int zero = 100000;
const int INF = 0x3f3f3f3f;
int main()
{
	int n;
	cin>>n;
	for(int x,y,i = 1; i<=n; i++) {
		scanf("%d%d",&x,&y);
		if(x < 0 && y < 0) continue;
		a[++tot] = x, b[tot] = y;
	} 
	for(int i = 0; i<=tot; i++) {
		for(int j = -100000; j<=300000; j++) {
			dp[i][j+ zero] = -INF;
		}
	}
	dp[0][zero] = 0;
//	for(int i = 1; i<=tot; i++) {
//		dp[i][a[i]+ zero] = b[i];
//	}
	for(int i = 1; i<=tot; i++) {
		for(int j = -100000; j<=100000; j++) {
			if(dp[i-1][j+ zero] != -INF) dp[i][j+ zero] = max(dp[i][j+zero],dp[i-1][j+ zero]);
			if(j - a[i] + zero >= 0 )
				dp[i][j+ zero] = max(dp[i][j+ zero] , dp[i-1][j-a[i]+ zero] + b[i]);
		}
	}
	int ans = 0;
	for(int j = 100000; j>=0; j--) {
		if(dp[tot][j+ zero] >= 0) ans = max(ans, dp[tot][j+ zero] + j);
	}
	printf("%d\n",ans);
	return 0 ;
 }

当然,如果你连if(j - a[i] + zero >= 0 )也不想写,那就可以直接ZERO设为200000就行了。

总结:

  首先需要知道他和0-1背包还是有区别的,因为0-1背包是可以不初始化成-INF的,但是那样表示的是可以表示的最大价值(因为价值都是正数),所以0可以当成是非法状态。而这个题,必须初始化成-INF,因为这题所谓的“价值”可以是负数,我们需要新设置一个非法状态,并且把唯一一个合法状态设置好dp[0][zero]=0;来方便后面的转移。其实这样说来,就可以改成一维的0-1背包了。

错误代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
int tot;
int a[MAX],b[MAX];
int dp[400000 + 5];//截止到第i个,a[i]和为j的情况下,b[i] 的最大值 
int zero = 100000;
const int INF = 0x3f3f3f3f;
int main()
{
	int n;
	cin>>n;
	for(int x,y,i = 1; i<=n; i++) {
		scanf("%d%d",&x,&y);
		if(x < 0 && y < 0) continue;
		a[++tot] = x, b[tot] = y;
	} 
	for(int j = -100000; j<=300000; j++) {
		dp[j+ zero] = -INF;
	}
	dp[zero] = 0;
	for(int i = 1; i<=tot; i++) {
		for(int j = 100000; j>=-100000; j--) {
			if(j - a[i] + zero >= 0) dp[j+ zero] = max(dp[j+ zero] , dp[j-a[i]+ zero] + b[i]);
		}
	}
	int ans = 0;
	for(int j = 100000; j>=0; j--) {
		if(dp[j+ zero] >= 0) ans = max(ans, dp[j+ zero] + j);
	}
	printf("%d\n",ans);
	return 0 ;
 }

但是仔细一想,这样是错误的,因为就地滚动的前提是,后面的数只能用到前面的数,而你这个题,a[i]有正有负,所以可能用到前面的状态也可能用到后面的状态,所以不能优化成一维。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值