「学习笔记」DP 学习笔记1

这篇具有很好参考价值的文章主要介绍了「学习笔记」DP 学习笔记1。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

序列DP

一般序列 DP 核心思想:将序列的前 \(i\) 个数的状态用一个更简单的形式表示出,并且体现出这些状态对后续的影响。

题目

ABC 267D
给定一个序列 \(a\),找到一个长度为 \(m\) 的子序列 \(b\),使得 \(\sum b_i × i\) 最大。
\(n, m \le 2 × 10^3\)

状态:\(f(i, j)\):前 \(i\) 个数,选 \(j\) 个数的最大和;
转移:\(f(i, j) = \max(f(i - 1, j), f(i - 1, j - 1) + a_i \times j)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 2010;

int n, m;
ll a[N], dp[N][N];

int main() {
	memset(dp, 128, sizeof dp);
	n = read(), m = read();
	for (int i = 1; i <= n; ++ i) {
		a[i] = read();
		dp[i][0] = 0;
	}
	dp[0][0] = 0;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= min(i, m); ++ j) {
			dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + 1ll * j * a[i]);
		}
	}
	printf("%lld\n", dp[n][m]);
	return 0;
}

B3637 最长上升子序列
给定一个序列,求它的最长上升子序列。\(n \le 5000\)

状态:\(dp_i\):最长上升子序列中第 \(i\) 个元素(是什么);
转移:如果 新元素 \(a\) 大于 \(dp_i\),则 \(dp_{i + 1} = a\),否则,二分出第一个大于等于 \(a\) 的前一个位置,替换上它。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

int n;
int y[100], dp[100];

int main() {
	n = read();
	for (int i = 1; i <= n; ++ i) {
		y[i] = read();
	}
	dp[1] = y[1];
	int cnt = 1;
	for (int i = 2; i <= n; ++ i) {
		if (y[i] > dp[cnt]) {
			dp[++ cnt] = y[i];
		}
		else {
			int p = lower_bound(dp + 1, dp + cnt + 1, y[i]) - dp;
			dp[p] = y[i];
		}
	}
	printf("%d\n", cnt);
	return 0;
}

P1439 【模板】最长公共子序列
给定两个 \(1, 2, 3 \cdots n\) 的序列 \(a, b\),求它们的最长公共子序列。
\(n \le 10^5\)

  • 朴素做法 \(O_{n^2}\)
    状态:\(dp(i, j)\):第一个串的前 \(i\) 位,第二个串的前 \(j\) 位的 LCS 的长度;
    如果当前的 \(a_i = b_j\) 相同(即是有新的公共元素) 那么 dp[i][j]=max(dp[i][j],dp[i−1][j−1])+1;;如果不相同,即无法更新公共元素,考虑继承:dp[i][j]=max(dp[i−1][j],dp[i][j−1])
#include<iostream>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 2010;

int n, m;
int dp[N][N], a1[N], a2[N];

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++ i) {
		a1[i] = read();
	}
	for (int i = 1; i <= m; ++ i) {
		a2[i] = read();
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= m; ++ j) {
			dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
			if (a1[i] == a2[j]) {
				dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
			}
		}
	}
	printf("%d\n", dp[n][m]);
	return 0;
}
  • 针对本题的 \(O_{n \log n}\) 做法。
    我们将 \(b\) 中的元素改为该元素在 \(a\) 中的位置,如果有一段位置是单调递增的,则这一段就是公共的子序列,我们再进行 DP。
    状态:\(dp_i\):最长公共子序列的第 \(i\) 个元素(是什么);
    转移:如果当前的 \(b_i\) 大于 \(dp_{last}\),则 \(dp_{last + 1} = b_i\),否则,二分找出大于等于 \(b_i\) 的前一个位置,替换。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1e5 + 5;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

int n;
ll a[N], b[N], l[N], dp[N];

int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		a[i] = read();
		l[a[i]] = i;
	}
	for (int i = 1; i <= n; ++i) {
		b[i] = read();
		b[i] = l[b[i]];
	}
	int len = 1, s;
	dp[1] = b[1];
	for (int i = 2; i <= n; ++i) {
		if (b[i] > dp[len]) {
			len ++;
			dp[len] = b[i];
		}
		else {
			s = lower_bound(dp + 1, dp + len + 1, b[i]) - dp;
			dp[s] = b[i];
		}
	}
	printf("%d", len);
	return 0;
}

区间DP

区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。
令状态 \(f(i,j)\) 表示将下标位置 \(i\)\(j\) 的所有元素合并能获得的价值的最大值,那么 \(f(i,j)=\max\{f(i,k)+f(k+1,j)+cost\}\)\(cost\) 为将这两组元素合并起来的代价。

题目

P1775 石子合并
\(n\) 堆石子,每堆石子有编号有重量,现在要将它们合并成一堆,只能合并相邻的两堆石子,且代价为两堆石子的重量和,求最小代价。\(n \le 300\)

状态:\(dp(i, j)\):合并 \(\left[ i, j \right]\) 的石子的最小代价。
转移:\(dp(i, j) = \min_{k = i}^{j - 1} \left\{ dp(i, k) + dp(k + 1, j) + cost(i, j) \right\}\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 410;

int n;
int a[N], dp[N][N], s[N];

int main() {
	memset(dp, 0x3f, sizeof dp);
	n = read();
	for (int i = 1; i <= n; ++ i) {
		a[i] = read();
		dp[i][i] = 0;
		s[i] = s[i - 1] + a[i];
	}
	for (int l = 2; l <= n; ++ l) {
		for (int i = 1; i + l - 1 <= n; ++ i) {
			int j = i + l - 1;
			for (int k = i; k < j; ++ k) {
				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]);
			}
		}
	}
	printf("%d\n", dp[1][n]);
	return 0;
}

P4170 [CQOI2007]涂色
有一条长度为 \(5\) 的木板,初始时没有涂过任何颜色。每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。用尽量少的涂色次数达到目标。\(n \le 50\)

状态:\(dp(i, j)\) 将木板 \(\left[ i, j \right]\) 涂成目标颜色的最小涂色次数。
转移:
如果 \(color_j = color_{j - 1}\),则 \(dp(i, j) = dp(i - 1, j)\)
否则 \(dp(i, j) = \min_{k = i}^{j - 1} \left \{ dp(i, k) + dp(k + 1, j) \right \}\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 100;

int n;
char s[N];
ll dp[N][N];

int main() {
	scanf("%s", s + 1);
	n = strlen(s + 1);
	memset(dp, 0x3f, sizeof dp);
	for (int i = 1; i <= n; ++ i)
		dp[i][i] = 1;
	for (int l = 1; l < n; ++ l) {
		for (int i = 1; i + l <= n; ++ i) {
			int j = i + l;
			if (s[i] == s[j]) {
				dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]);
			} else {
				for (int k = i; k < j; ++ k)
					dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
			}
		}
	}
	printf("%lld\n", dp[1][n]);
	return 0;
}

P1220 关路灯
在一条路线上安装了 \(n\) 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。现在已知老张走的速度为 \(1m/s\),每个路灯的位置(是一个整数,即距路线起点的距离,单位:\(m\))、功率(\(W\)),老张关灯所用的时间很短而可以忽略不计。问:怎样最省电?\(n \le 50\)

考虑对于状态的设计,关掉一段区间的灯,需要存下左右端点,需要两维,对于关掉区间 \(\left [ l, r \right ]\),最后一次只可能是关掉 \(l\) 位置的灯或者 \(r\) 位置的灯,即最后停留的位置有最左端与最右端两种,且对答案有影响,再加一维来存这两种情况。
状态:\(dp(i, j, 0/1)\)
转移:\(dp(i, j, 0) = \min(dp(i + 1, j, 0) + cost_1, dp(i + 1, j, 1) + cost_2\)),
\(dp(i, j, 1) = \min(dp(i, j - 1, 0) + cost_1, dp(i, j - 1, 1) + cost_2)\)
初始化:\(dp(c, c, 0) = 0, dp(c, c, 1) = 0\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int f = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		f |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return f ? ~x + 1 : x;
}

const int N = 110;

int n, c, sum, ans;
int a[N], w[N], s[N];
int dp[N][N][2];
bool b[N];

int main() {
	n = read(), c = read();
	for (int i = 1; i <= n; ++ i) {
		a[i] = read(), w[i] = read();
		s[i] = s[i - 1] + w[i];
	}
	memset(dp, 0x3f, sizeof dp);
	dp[c][c][0] = 0;
	dp[c][c][1] = 0;
	for (int j = c; j <= n; ++ j) {
		for (int i = j - 1; i >= 1; -- i) {
			dp[i][j][0] = min(dp[i + 1][j][0] + (a[i + 1] - a[i]) * (s[n] + s[i] - s[j]), dp[i + 1][j][1] + (a[j] - a[i]) * (s[n] + s[i] - s[j]));
			dp[i][j][1] = min(dp[i][j - 1][0] + (a[j] - a[i]) * (s[n] + s[i - 1] - s[j - 1]), dp[i][j - 1][1] + (a[j] - a[j - 1]) * (s[n] + s[i - 1] - s[j - 1]));
		}
	}
	ans = min(dp[1][n][0], dp[1][n][1]);
	printf("%d", ans);
	return 0;
}

P3146 [USACO16OPEN]248 G
给定一个 \(1 \times n\) 的地图,在里面玩 2048,每次可以合并相邻两个(数值范围 \(1 \sim 40\)),问序列中出现的最大数字的值最大是多少。注意合并后的数值并非加倍而是 \(+1\),例如 \(2\)\(2\) 合并后的数值为 \(3\)\(2 \le n \le 248\)

这里要考虑 2048 的游戏规则,即只有两个相邻的数相等才能合。
状态:\(dp(i, j)\):合并区间 \(\left [ i, j \right ]\) 后的最大数值。
转移:\(dp(i, j) = \max(dp(i, j), dp(k + 1, j) + 1)\),条件:\(dp(i, k) = dp(k + 1, j)\),这里要注意,如果 \(dp(k + 1, j)\)\(0\),说明这段区间没被更新到,因此答案也不可能为 \(1\),应该为 \(0\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 510;

int n, ans;
int a[N];
int dp[N][N];

int main() {
	n = read();
	for (int i = 1; i <= n; ++ i) {
		a[i] = read();
		dp[i][i] = a[i];
	}
	for (int len = 1; len <= n - 1; ++ len)
		for (int i = 1; i <= n - len; ++ i) {
			int j = i + len;
			for (int k = i; k < j; ++ k) {
				if (dp[k + 1][j] > 0 && dp[i][k] == dp[k + 1][j]) {
					dp[i][j] = max(dp[i][j], dp[k + 1][j] + 1);
					ans = max(ans, dp[k + 1][j] + 1);
				}
			}
		}
	printf("%d", ans);
	return 0;
}

P3205 [HNOI2010]合唱队
合唱队一共 \(n\) 个人,第 \(i\) 个人的身高为 \(h_i\) 米(\(1000 \le h_i \le 2000\)),并已知任何两个人的身高都不同。假定最终排出的队形是 \(A\) 个人站成一排,为了简化问题,小 A 想出了如下排队的方式:他让所有的人先按任意顺序站成一个初始队形,然后从左到右按以下原则依次将每个人插入最终棑排出的队形中: 第一个人直接插入空的当前队形中;对从第二个人开始的每个人,如果他比前面那个人高(\(h\) 较大),那么将他插入当前队形的最右边。如果他比前面那个人矮(\(h\) 较小),那么将他插入当前队形的最左边。当 \(n\) 个人全部插入当前队形后便获得最终排出的队形。答案要对 \(19650827\) 取模,\(n \le 1000\)\(1000 \le h_i \le 2000\)

这道题与关路灯那道题差不多,在状态设计上要考虑上一个被插入的人是插入的左边还是右边。
状态:\(dp(i, j, 0/1)\):区间 \(\left[ i, j \right]\) 中,最后一个人被插入了 \(0\) 左边 / \(1\) 右边。
转移:
如果 \(a_i < a_j\),那么 \(dp(i, j, 0) = dp(i, j, 0) + dp(i + 1, j, 1), dp(i, j, 1) = dp(i, j, 1) + dp(i, j - 1, 0)\)
如果 \(a_i < a_{i + 1}\),那么 \(dp(i, j, 0) = dp(i, j, 0) + dp(i + 1, j, 0)\)
如果 \(a_j > a_{j - 1}\),那么 \(dp(i, j, 1) = dp(i, j, 1) + dp(i, j - 1, 1)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 2010;
const int mod = 19650827;

int dp[N][N][2], a[N];

int main() {
	int n;
	n = read();
	for (int i = 1; i <= n; ++ i)
		a[i] = read();
	for (int i = 1; i <= n; ++ i)
		dp[i][i][0] = 1;
	for (int i = n - 1; i >= 1; -- i) {
		for (int j = i + 1; j <= n; ++ j) {
			if (a[i] < a[j]) {
				dp[i][j][0] += dp[i + 1][j][1];
				dp[i][j][1] += dp[i][j - 1][0];
				dp[i][j][0] %= mod;
				dp[i][j][1] %= mod;
			}
			if (a[i] < a[i + 1]) {
				dp[i][j][0] += dp[i + 1][j][0];
				dp[i][j][0] %= mod;
			}
			if (a[j] > a[j - 1]) {
				dp[i][j][1] += dp[i][j - 1][1];
				dp[i][j][1] %= mod;
			}
		}
	}
	printf("%d", (dp[1][n][0] + dp[1][n][1]) % mod);
	return 0;
}

P5336 [THUSC2016]成绩单
L 老师共有 \(n\) 份成绩单,其中编号为 \(i\) 的的成绩单分数为 \(W_i\)。发放成绩单时,L 老师会从当前的一叠成绩单中抽取连续的一段,让这些同学来领取自己的成绩单。当这批同学领取完毕后,L 老师再从剩余的成绩单中抽取连续的一段,供下一批同学领取。经过若干批次的领取后,成绩单将被全部发放到同学手中。不能让分数相差太远的同学在同一批领取成绩单;尽量减少领取成绩单的批次数。对于一个分发成绩单的方案,我们定义其代价为:\(a \times k + b \times \sum_{i = 1} ^ k (max_i - min_i) ^ 2\),其中 \(k\) 是分发的批次数,对于第 \(i\) 披分发的成绩单,\(max_i\) 是最高分数,\(min_i\) 是最低分数,\(a\)\(b\) 是给定的评估参数,求最小代价。
\(n \leq 50\)\(a \leq 1500\)\(b \leq 10\)\(w_i \leq 1000\)

区间DP,设 \(ans(l, r)\) 为区间 \([l, r]\) 全部选择的最小代价。
但是,我们发现,影响我们最终答案的不只是 \(l, r\),还有剩余部分的最大值和最小值,所以还要再加两维。
为了方便,我们直接新开一个数组 \(dp(l, r, i, j)\),表示在区间 \([l, r]\) 选完后剩余部分的最大值为 \(i\),最小值为 \(j\) 的最小代价。
由于 \(n\) 很小,但 \(w_i \le 1000\),所以需要离散化。
转移:
\(dp(l, r, i, j) = \min(dp(l, r, i, j), dp(l, k, i, j) + ans(k + 1, r)) 左边留下 i 和 j,右边全选\)
\(dp(l, r, i, j) = \min(dp(l, r, i, j), ans(l, k) + dp(k + 1, r, i, j)) 右边留下 i 和 j,左边全选\)
\(dp(l, r, i, j) = \min(dp(l, r, i, j), dp(l, k, i, j) + dp(k + 1, r, i, j)) 左右两边都留下 i 和 j\)
\(l = r\) 时,还要注意初始化,设置好边界以防越界。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 60;

int n, a, b, m;
int w[N], v[N];
int ans[N][N], dp[N][N][N][N];

void dfs(int l, int r) {
	if (l > r)	return ;
	if (ans[l][r])	return ;
	if (l == r) {
		ans[l][r] = a;
		for (int i = 1; i <= w[l]; ++ i) {
			for (int j = i; j <= w[l] - 1; ++ j) {
				dp[l][r][i][j] = 1e9 + 5;
			}
		}
		for (int i = w[l] + 1; i <= m; ++ i) {
			for (int j = i; j <= m; ++ j) {
				dp[l][r][i][j] = 1e9 + 5;
			}
		}
		return ;
	}
	ans[l][r] = 1e9 + 5;
	for (int i = 1; i <= m; ++ i) {
		for (int j = i; j <= m; ++ j) {
			int res = 1e9 + 5;
			for (int k = l; k < r; ++ k) {
				dfs(l, k), dfs(k + 1, r);
				res = min(res, ans[l][k] + dp[k + 1][r][i][j]);
				res = min(res, dp[l][k][i][j] + ans[k + 1][r]);
				res = min(res, dp[l][k][i][j] + dp[k + 1][r][i][j]);
			}
			dp[l][r][i][j] = res;
			ans[l][r] = min(ans[l][r], res + a + b * (v[j] - v[i]) * (v[j] - v[i]));
		}
	}
}

int main() {
	n = read();
	a = read(), b = read();
	for (int i = 1; i <= n; ++ i) {
		v[i] = w[i] = read();
	}
	sort(v + 1, v + n + 1);
	m = unique(v + 1, v + n + 1) - v - 1;
	for (int i = 1; i <= n; ++ i) {
		w[i] = lower_bound(v + 1, v + m + 1, w[i]) - v;
	}
	dfs(1, n);
	printf("%d\n", ans[1][n]);
	return 0;
}

环状DP

在环上的 DP,基本方法就是断环为链,然后把它作为区间 DP 或 序列 DP 去做。
作为区间 DP 做时有一个小技巧就是将 这个链复制两遍,以防止首位的一些非法情况被我们计算。

P1880 [NOI1995] 石子合并
石子合并,但是,是在环上。\(n \le 100\)

断环为链,复制两遍这条链转化为区间 DP。
状态:\(f1(i, j)\):区间 \(\left[ i, j \right ]\) 的最大得分,\(f2(i, j)\):区间 \(\left[ i, j \right ]\) 的最小得分。
转移:\(f1(i, j) = \max_{k = i}^{j - 1}\{f1(i, j), f1(i, k) + f1(k + 1, j) + cost\}\)
\(f2(i, j) = \min_{k = i}^{j - 1}\{f2(i, j), f2(i, k) + f2(k + 1, j) + cost\}\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

ll r[600], g[600];
ll f1[600][600], f2[600][600];

int main() {
	int n;
	n = read();
	for (int i = 1; i <= n; ++i) {
		r[i] = read();
		r[i + n] = r[i];
		g[i] = g[i - 1] + r[i];
		f1[i][i] = f2[i][i] = 0;
	}
	for (int i = n + 1; i <= 2 * n; ++i) {
		f2[i][i] = 0;
		g[i] = g[i - 1] + r[i];
	}
	for (int l = 1; l < n; ++l) {
		for (int i = 1, j = i + l; i < n * 2 && j <= n * 2; ++i, j = i + l) {
			f2[i][j] = 100000000;
			for (int k = i; k < j; ++k) {
				f1[i][j] = max(f1[i][j], f1[i][k] + f1[k + 1][j] + g[j] - g[i - 1]);
				f2[i][j] = min(f2[i][j], f2[i][k] + f2[k + 1][j] + g[j] - g[i - 1]);
			}
		}
	}
	ll maxn = 0, minn = 1e18;
	for (int i = 1; i <= n; ++i) {
		maxn = max(maxn, f1[i][i + n - 1]);
		minn = min(minn, f2[i][i + n - 1]);
	}
	printf("%lld\n%lld", minn, maxn);
	return 0;
}

P1063 [NOIP2006 提高组] 能量项链
给定一个有 \(n\) 个珠子的项链,每个珠子有头标记和尾标记,相邻两个珠子,前一个珠子的尾标记等于后一个珠子的头标记,只有相邻的两个柱子能合并成一个,并产生能量,能量为 \(前一颗珠子的头标记 \times 后一颗珠子的头标记 \times 后一颗珠子的尾标记\),合并后的新珠子,头标记等于前一颗珠子的头标记,尾标记等于后一颗珠子的尾标记。求最大能量。\(4 \le n \le 400\)

很经典的环形 DP 题。
状态:\(dp(i, j)\): 合并区间 \(\left[ i, j \right]\) 释放的最大能量。
转移:\(dp(i, j) = \max_{k = i}^{j - 1}\{dp(i, k) + dp(k + 1, j) + a_i \times a_{k + 1} \times a_{j + 1} \}\)文章来源地址https://www.toymoban.com/news/detail-480358.html

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

int n;
ll a[300], f[300][300];
ll maxn;

int main() {
	n = read();
	for (int i = 1; i <= n; ++ i) {
		a[i] = read();
		a[i + n] = a[i];
	}
	for (int i = 2; i < 2 * n; ++ i)
		for (int j = i - 1; i - j < n && j >= 1; -- j)
			for (int k = j; k < i; ++ k) {
				f[j][i] = max(f[j][i], a[j] * a[k + 1] * a[i + 1] + f[j][k] + f[k + 1][i]);
				if (f[j][i] > maxn)	maxn = f[j][i];
			}
	printf("%lld", maxn);
	return 0;
}

到了这里,关于「学习笔记」DP 学习笔记1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 斜率优化DP 学习笔记

    适用于求解最优解(最大、最小)问题。 可以将转移方程可以化为 (left[begin{array}{rl} 仅与 space i space 有关 是我们想要最大/最小化的 \\\\ 仅与 space j space 有关 是已知的 \\\\ 与 space i space 和 space j space 都有关 是两项相乘 end{array}right]) 三部分的, 都可以考虑用斜率优化

    2024年02月09日
    浏览(32)
  • 「学习笔记」数位 DP

    意义不大的题不写了。 点击查看目录 目录 「学习笔记」数位 DP 概述 例题 P2657 [SCOI2009] windy 数 思路 代码 P4317 花神的数论题 思路 P4124 [CQOI2016]手机号码 思路 代码 haha数 题意 思路 代码 0和1的熟练 题意 思路 代码 苍与红的试炼 题意 思路 代码 数位 DP 一般用来解决「在一个较

    2023年04月09日
    浏览(30)
  • 动态规划——树形DP 学习笔记

    前置知识:树基础。 树形 DP,即在树上进行的 DP,最常见的状态表示为 (f_{u,cdots}) ,表示以 (u) 为根的子树的某个东东。 本文将讲解一些经典题目(树的子树个数、树的最大独立集、树的最小点覆盖、树的最小支配集、树的直径、树的重心、树的中心),以及一些常见形

    2024年02月08日
    浏览(30)
  • 动态规划——区间DP 学习笔记

    不含四边形不等式优化。 线性动态规划的局限性在于,它只能顺推或倒退,而不能有子区间依赖的问题。 区间动态规划是线性动态规划的扩展,它将问题划分为若干个子区间,并通过定义状态和状态转移方程来求解每个子区间的最优解,最终得到整个区间的最优解。 区间动

    2024年02月08日
    浏览(33)
  • 动态规划——状压DP 学习笔记

    前置知识:位运算 动态规划的过程是随着阶段的增长,在每个状态维度上不断扩展的。 在任意时刻,已经求出最优解的状态与尚未求出最优解的状态在各维度上的分界点组成了 DP 扩展的“轮廓”。对于某些问题,我们需要在动态规划的“状态”中记录一个集合,保存这个“

    2024年02月08日
    浏览(45)
  • 动态规划——数位DP 学习笔记

    引入 数位 DP 往往都是这样的题型:给定一个区间 ([l, r]) ,求这个区间中满足某种条件的数的总数。 简单的暴力代码如下: 而当数据规模过大,暴力枚举就 (mathbb T) 飞了,因此引入数位 DP: 概念 数位(digit):对于十进制,即把一个数字按照个位、十位、百位等,一位

    2024年02月08日
    浏览(41)
  • 【数位dp】【动态规划】C++算法:233.数字 1 的个数

    视频算法专题 动态规划汇总 给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。 示例 1: 输入:n = 13 输出:6 示例 2: 输入:n = 0 输出:0 提示: 0 = n = 10 9 本题比较简单,主要讲封装类。m_vPre记录上一位所有状态,程序结束时,记录的是最后一位的所有

    2024年01月16日
    浏览(32)
  • 动态规划——斜率优化DP 学习笔记

    适用于求解最优解(最大、最小)问题。 可以将转移方程可以化为 (left[begin{array}{rl} 仅与 space i space 有关 是我们想要最大/最小化的 \\\\ 仅与 space j space 有关 是已知的 \\\\ 与 space i space 和 space j space 都有关 是两项相乘 end{array}right]) 三部分的, 都可以考虑用斜率优化

    2024年02月08日
    浏览(42)
  • 动态规划——矩阵优化DP 学习笔记

    前置知识:矩阵、矩阵乘法。 斐波那契数列 在斐波那契数列当中, (f_1 = f_2 = 1) , (f_i = f_{i - 1} + f_{i - 2}) ,求 (f_n) 。 而分析式子可以知道,求 (f_k) 仅与 (f_{k - 1}) 和 (f_{k - 2}) 有关; 所以我们设矩阵 (F_i = begin{bmatrix} f_{i - 1} f_{i - 2} end{bmatrix}) 。 设矩阵 (text{Ba

    2024年02月08日
    浏览(48)
  • 动态规划——带权二分优化DP 学习笔记

    带权二分其实并不一定用于优化 DP,也可能用于优化贪心等最优化的算法。 带权二分也叫 WQS 二分,最初由王钦石在他的 2012 年国家集训队论文中提出。 使用情况 要解决一个最优化问题(求最大 / 最小值) 有一个限制,一般是某个参数要求一定恰好为 (k) 而带权二分就可以

    2024年02月08日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包