DP成魔之路——梦开始的地方

DP手的诞生

DP是这样的,出题人只要出题就好了,而做题的就要想很多了

喜欢和自己博弈,所以我要选DP

我有一道DP,你要不试试

又菜又爱,码垛

关注我,教我彻底学会DP

DP题集收纳

image-20240423153237332

背包——背不出来

三状态背包

目标状态有三,你要怎么用,用谁,转换方程怎么写

**Problem:**n个食物,每个食物有a,b两个属性,你的胃两个属性的最大值分别是x,y,问最多能糟踏多少食物(咬一口也算糟蹋了)?

传送门E - Maximum Glutton (atcoder.jp)

Ideas:错误思路:正常来讲,我会直接把他当作一个普通的01背包,然后去贪心看看(分别用a,b属性对食物排序),果不其然wa了,后来就在想,用最小的替换掉大的,第i个物品作为二维数组变量1,a属性作为二维数组变量2,b属性作为二维数组的值1,个数作为值2,但是细想之后还是不对,因为用哪个来排序都行)(那就来说说正解:前文提到,二维数组有四个状态,但是b和个数谁来排序都不对,那么应该怎么做?前文是不是有个可有可无的变量呢?第几个物品,重要吗?好像,嗯…没用上啊那删掉,现在我总不能再拿b属性作为二维数组的变量1吧,那时间复杂度就超了,把个数当作变量1怎么样?然后只剩b数组作为值,斯,那这么说,如果个数能装到,并且使用a属性的时候让b属性尽可能小,那么,出现过的个数属性就是答案了!!!

理论形成,转换开始:

转换图

Code:

#include <bits/stdc++.h>
#define int long long
#define endl "\n"

using namespace std;

const int N = 10010;

typedef long long LL;
typedef pair<int, int> PII;

int n;
int a[N], b[N];
int dp[100][N];
int x, y;

void solve() {
	cin >> n;
	cin >> x >> y;
	for(int i = 1; i <= n; i++) cin >> a[i] >> b[i];
	
	memset(dp, 0x3f, sizeof dp);
	dp[0][0] = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = n; j; j--) {
			for(int k = a[i]; k <= x; k++) {
				if(dp[j - 1][k - a[i]] + b[i] <= y) dp[j][k] = min(dp[j][k], dp[j - 1][k - a[i]] + b[i]);
				
			}
		}
	}
	
	for(int i = n; i >= 0; i--) {
		for(int j = 0; j <= x; j++) {
			if(dp[i][j] <= 1e9) {
				cout << i + (i == n ? 0 : 1) << endl;
				return ;
			}
		}
	} 
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(0);
	cout.tie(0);
	
	int t = 1;
	// cin >> t;
	while(t--) {
		solve();
	}
}

序列和矩阵DP优化

三状态转移

Problem: 给定一个矩阵,要求每一行都选择一个区间,并在相邻行的区间有交,最大化选中的数字和

碎碎念:赛时的时候想到了状态转移的方法,但是没有想清楚状态转移的方程,还是太菜了

传送门: I-Intersecting Intervals_2024牛客暑期多校训练营6 (nowcoder.com)

Ideas: 对于dp[i][j] 的定义为第i行强制选择第j个作为最大化的值,那么,不难发现,它可以从三个方向进行状态转移。上面,左边,右边。另外需要两个数组,pre表示该行以j作为结尾的最大子序列pre[j] = max(pre[j - 1] + a[i][j], a[i][j]),suf表示以改行以j作为开始的最大子序列 suf[j] = max(suf[j + 1] + a[i][j], a[i][j]) ,那么,从上面的转移就很清楚了,dp[i][j] = d[i - 1][j] + pre[j] + suf[j] - a[i][j] ,那么,左边的转移又怎么处理?用一个l数组表示该行以j结尾的最大子序列,去存储不考虑该行j~m的值,因为suf[j] 里面存储了右边的最大连续子序列,那么l[j] = max(l[j - 1] + a[i][j], dp[i - 1][j] + pre[j]); 对于dp[i][j] 来说的转移就是 dp[i][j] = max(dp[i][j], l[j] + suf[j] - a[i][j]); 右边同理。

在这里插入图片描述

Code

void solve() {
	int n, m;
	cin >> n >> m;
	vector<vector<int>> a(n + 2, vector<int> (m + 2));
	vector<vector<int>> dp(n + 2, vector<int> (m + 2));
	
	for(int i = 1; i <= n; i++) {
		vector<int> pre(m + 2), suf(m + 2);
		vector<int> l(m + 2), r(m + 2);
		pre[0] = 0, suf[m + 1] = 0;
		for(int j = 1; j <= m; j++) {
			cin >> a[i][j];
		}
		for(int j = 1; j <= m; j++) {
			pre[j] = max(pre[j - 1] + a[i][j], a[i][j]);
		}
		for(int j = m; j >= 1; j--) {
			suf[j] = max(suf[j + 1] + a[i][j], a[i][j]);
		}
		l[0] = -1e18, r[m + 1] = -1e18;
		for(int j = 1; j <= m; j++) {
			l[j] = max(l[j - 1] + a[i][j], dp[i - 1][j] + pre[j]);
			dp[i][j] = l[j] + suf[j] - a[i][j];
		}
		for(int j = m; j >= 1; j--) {
			r[j] = max(r[j + 1] + a[i][j], dp[i - 1][j] + suf[j]);
			dp[i][j] = max(dp[i][j], r[j] + pre[j] - a[i][j]);
		}
	}
	
	int res = -1e18;
	for(int i = 1; i <= m; i++) {
		res = max(res, dp[n][i]);
	}
	cout << res << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值