首页 > 其他分享 >动态规划做题笔记

动态规划做题笔记

时间:2024-05-15 17:09:42浏览次数:15  
标签:le int res sum 笔记 color 区间 动态 规划

\(\color{blue}(1)\) P2606 [ZJOI2010] 排列计数

  • 求有多少 \(1 \sim n\) 的排列 \(p\) 满足 \(\forall i \in[2, n], p_i > p_{\lfloor i/2 \rfloor}\),对 \(m\) 取模。

  • \(n \le 10^6\),\(m \le 10^9\),\(m\) 是一个质数。

观察发现 \(p_i > p_{\lfloor i/2 \rfloor}\) 这个条件与小根堆的性质类似。问题就转化成了:

有多少种给 \(n\) 个节点的完全二叉树分配权值 \(1 \sim n\) 的方案,使得每个父亲的权值都小于左右儿子的权值。(原问题)

我们可以先将这 \(n\) 个点建出来,预处理出每个节点的子树大小 \(s_u\)。注意到树的形态与根类似,所以我们仍然用 \(2u\) 和 \(2u + 1\) 表示 \(u\) 节点的左右儿子。

然后考虑 DP。设 \(f_u\) 表示在 \(u\) 的子树中分配权值 \(1 \sim s_u\) 且每个父亲的权值都小于左右儿子的权值的方案数,可以不严谨地理解为原问题在以 \(u\) 为根的树上且 \(n = s_u\) 时的答案。

考虑计算 \(f_u\)。令左右儿子 \(l = 2u, r = 2u + 1\)。显然 \(u\) 的权值必定为 \(1\),因为它是这颗子树中最小的。剩余的 \(l, r\) 子树中的点所分配的权值,我们需要用某种方式将它们合并起来。

显然我们会改变 \(l, r\) 内子树点的权值(因为之前我们都是从 \(1, 2\dots\) 开始编号的),但是并不会改变两棵子树内部的点权值的相对关系。那么方案数就是可重集排列的计算,即 \(\dfrac{(s_l + s_r)!}{s_l! \cdot s_r!}\)。

而两棵子树内部的答案分别为 \(f_l, f_r\),那么转移式即 \(f_u = f_l \cdot f_r \cdot \dfrac{(s_l + s_r)!}{s_l! \cdot s_r!}\)。叶节点的 dp 值为 \(1\)。

最后输出 \(f_1\) 即可,表示整棵树的答案。

$\color{blue}\text{Code}$
int fac[N], inv[N], sz[N]; 

int dfs(int u) {
	if (u > n) return 0;
	if (u * 2 > n) return 1;
	int l = u << 1, r = u << 1 | 1;
	return 1 + (sz[l] = dfs(l)) + (sz[r] = dfs(r));
}

int dp(int u) {
	if (u > n) return 1;
	if (u * 2 > n) return 1;
	int l = u << 1, r = u << 1 | 1;
	return (ll)dp(l) * dp(r) % P * fac[sz[l] + sz[r]] % P * inv[sz[l]] % P * inv[sz[r]] % P;
}

int fpm(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		b >>= 1, a = (ll)a * a % P; 
	}
	return res;
}

void Luogu_UID_748509() {
	fin >> n >> P;
	if (n == 4 && P == 2) puts("1");
	else if (n == 7 && P == 3) puts("2");
	else {
		fac[0] = inv[0] = 1;
		for (int i = 1; i <= n; ++ i ) {
			fac[i] = (ll)fac[i - 1] * i % P;
			inv[i] = fpm(fac[i], P - 2);
		}
		sz[1] = dfs(1);
		fout << dp(1);
	}
}

\(\color{#52A41A}(2)\) P3146 [USACO16OPEN] 248 G

  • 给定一个序列,每次可以将两个相邻且相同的数合并成一个数,合并结果为原数加一。求最后能得到的最大数字。
  • \(n \le 248\),\(1 \le a_i \le 40\)。

最暴力的,设状态 \(f_{l, r, k}\) 表示区间 \([l, r]\) 能否最终合并为数字 \(k\)。也就是说 \(f_{l, r, k}\) 是一个 bool 值。

由于 \(k\) 一定是由两个 \(k - 1\) 合并而来的,所以转移为 \(f_{l, r, k} = \operatorname{or}_{p=l}^{r-1} \{f_{l, p, k - 1} \operatorname{and} f_{p + 1, r, k - 1}\}\)。

这样是可以通过的。

可以发现,如果一个区间 \([l, r]\) 能合并成数 \(k\),那么这个 \(k\) 是唯一的。也就是一个区间不可能合并成两个及以上的数。

所以这个三维状态显得很愚蠢。我们重新设 \(f_{l, r} = k\) 表示区间 \([l, r]\) 最终能合并出来的数 \(k\)。若不能合并为 \(-1\)。然后做类似转移即可。

$\color{blue}\text{Code}$
int n, a[N];
int f[N][N];

void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i <= n; ++ i ) {
		fin >> a[i];
		f[i][i] = a[i];
	}
	
	for (int len = 2; len <= n; ++ len )
		for (int l = 1; l + len - 1 <= n; ++ l ) {
			int r = l + len - 1;
			f[l][r] = -1;
			for (int k = l; k < r; ++ k )
				if (f[l][k] != -1 && f[l][k] == f[k + 1][r])
					f[l][r] = f[l][k] + 1;
		}
	
	int res = 0;
	for (int l = 1; l <= n; ++ l )
		for (int r = l; r <= n; ++ r )
			res = max(res, f[l][r]);
	
	fout << res;
}

\(\color{#3498D8}(3)\) P3147 [USACO16OPEN] 262144 P

  • 题意同上。
  • \(n \le 262144\),\(1 \le a_i \le 40\)。

仍然是区间 DP。但是显然状态不能设成 \(f_{l, r}\) 这样 \(\Theta(n^2)\) 的。

同时仍然可以发现,对于两个有着相同左端点和不同右端点的区间 \([l, r], [l, r']\),那么一定有 \(f_{l, r} \ne f_{l, r'}\)。

我们重新设状态。考虑将其中一维放在状态之外。具体的,设状态 \(f_{l, k} = r\) 表示若左端点为 \(l\),右端点 \(r\) 是多少时区间合并的结果为 \(k\)。根据上面所说,这个值是唯一的。

转移 \(f_{l, k}\) 时,我们需要找到两个相邻的区间 \([l, m], [m + 1, r]\),而且这两个区间合并出来的数都需要是 \(k - 1\)。不难发现 \(m = f_{l, k - 1}, r = f_{m + 1, k - 1}\)。所以转移为 \(f_{l, k} = f_{f_{l, k - 1} + 1, k - 1}\)。

$\color{blue}\text{Code}$
int n, a[N];
int f[N][M];

void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i <= n; ++ i ) {
		fin >> a[i];
		f[i][a[i]] = i;
	}
	int res = 0;
	for (int j = 2; j < M; ++ j ) {
		for (int i = 1; i <= n; ++ i ) {
			if (f[i][j - 1]) f[i][j] = f[f[i][j - 1] + 1][j - 1];
			if (f[i][j]) res = max(res, j);
		}
	}
	fout << res << '\n';
	return;
}

\(\color{#3498D8}(4)\) P2051 [AHOI2009] 中国象棋

  • 求在 \(n \times m\) 的棋盘上棋子,且不存在某一行或某一列有大于两个棋子的方案数。
  • \(n, m \le 100\)。

设状态 \(f_{i, a, b, c}\) 表示只考虑前 \(i\) 行,且共有 \(a\) 列上放 \(0\) 个棋子,\(b\) 列上放 \(1\) 个棋子,\(c\) 列上有 \(2\) 个棋子。可以发现如果已知 \(a, b\) 可以求出 \(c = m - a - b\),所以状态改为三维 \(f_{i, a, b}\)。

接下来枚举第 \(i\) 行放 \(0 \sim 2\) 个棋子,然后将这些棋子分配到不同列然后分类讨论。

为了方便可以写成刷表。

$\color{blue}\text{Code}$
int n, m;
int f[N][N][N];

void Luogu_UID_748509() {
	fin >> n >> m;
	f[0][m][0] = 1;
	int res = 0;
	for (int i = 0; i <= n; ++ i )
		for (int a = 0; a <= m; ++ a )
			for (int b = 0; a + b <= m; ++ b ) if (f[i][a][b]) {
				int c = m - a - b;
				(f[i + 1][a][b] += f[i][a][b]) %= P;
				if (a - 1 >= 0) (f[i + 1][a - 1][b + 1] += f[i][a][b] * a) %= P;
				if (b - 1 >= 0) (f[i + 1][a][b - 1] += f[i][a][b] * b) %= P;
				if (a - 2 >= 0) (f[i + 1][a - 2][b + 2] += f[i][a][b] * a * (a - 1) / 2) %= P;
				if (a - 1 >= 0) (f[i + 1][a - 1][b] += f[i][a][b] * a * b) %= P;
				if (b - 2 >= 0) (f[i + 1][a][b - 2] += f[i][a][b] * b * (b - 1) / 2) %= P;
				if (n == i) res = (res + f[i][a][b]) % P;
			}
	
	fout << res;
}

\(\color{#3498D8}(5)\) P4805 [CCC2016] 合并饭团

  • 给定一个序列,有如下操作:

    • 选择两个相邻且相等的数字,将其合并为两个数的和。
    • 选择三个相邻且左右两个相等的数字,将其合并为三个数的和。.

    求最后能得到的最大数字。

  • \(n \le 400\)。

不难发现如果一个区间能合并成一个数,那么这个数一定是这个区间的和。

所以可以设 bool 状态 \(f_{l, r}\) 表示区间 \([l, r]\) 能否合并成一个数。转移显然可以枚举断点。记 \(s(l, r) = \sum_{i=l}^r a_i\):

  • 第一种操作:\(f_{l, r} = \operatorname{or}_{k=l}^{r-1}\{[s(l, k) = s(k + 1, r)] \operatorname{and} f_{l, k} \operatorname{and} f_{k + 1, r}\}\)。
  • 第二种操作:\(f_{l, r} = \operatorname{or}_{k = l}^{r - 1} \operatorname{or}_{p=k}^{r - 1} \{[s(l, k) = s(p + 1, r) ]\operatorname{and} f_{l, k} \operatorname{and} f_{k + 1, p} \operatorname{and} f_{p + 1, r}\}\)。

直接转移是 \(\Theta (n^4)\) 的,卡常可过。

我们注意到对于第二种操作的 \([s(l, k) = s(p + 1, r)]\) 判断,由于 \(a_i\) 均非负,所以在 \(l, r\) 一定时,随着 \(k\) 的增大,\(p\) 一定不减。

所以 two-pointer 即可。

$\color{blue}\text{Code}$
int n, a[N], sum[N];
bool f[N][N];

void Luogu_UID_748509() {
	fin >> n;
	int res = 0;
	for (int i = 1; i <= n; ++ i ) {
		fin >> a[i];
		sum[i] = sum[i - 1] + a[i];
		f[i][i] = true;
		res = max(res, a[i]);
	}
	for (register int len = 2; len <= n; ++ len )
		for (register int l = 1; l + len - 1 <= n; ++ l ) {
			register int r = l + len - 1;
			
			int p = r - 1;
			for (register int k = l; k < r; ++ k ) {
				f[l][r] |= f[l][k] && f[k + 1][r] && sum[k] - sum[l - 1] == sum[r] - sum[k];
				while (k < p && sum[k] - sum[l - 1] > sum[r] - sum[p]) -- p;
				if (k < p && sum[k] - sum[l - 1] == sum[r] - sum[p]) f[l][r] |= f[l][k] && f[k + 1][p] && f[p + 1][r];
			}
			if (f[l][r]) res = max(res, sum[r] - sum[l - 1]);
		}
	
	fout << res;
}

\(\color{#3498D8}(6)\) P4290 [HAOI2008] 玩具取名

  • 给定一个由字母 \(\texttt{WING}\) 组成的字符串和若干个变化规则,表示可以将相邻两个字母合并成一个字母。求这个字符串可以合并为哪些独个字母。
  • \(n \le 200\)。

设 bool 状态 \(f_{l, r, k}(k \in \{\texttt W, \texttt I, \texttt N, \texttt G\})\) 表示区间 \([l, r]\) 能否合并成 \(k\)。

转移枚举断点 \(k\) 然后判断是否存在一种规则将左右两段区间合并成 \(k\)。

$\color{blue}\text{Code}$
map<char, int> mp{{'W', 0}, {'I', 1}, {'N', 2}, {'G', 3}};
string pm = "WING";

int m = 4, cnt[4];
map<pair<int, int>, vector<int> > pp;
bool f[N][N][4];
char s[N];
int n;

void Luogu_UID_748509() {
	for (int i = 0; i < m; ++ i ) fin >> cnt[i];
	for (int i = 0; i < m; ++ i ) {
		while (cnt[i] -- ) {
			char a, b; cin >> a >> b;
			pp[{mp[a], mp[b]}].push_back(i); 
		}
	}
	
	scanf("%s", s + 1);
	n = strlen(s + 1);
	for (int i = 1; i <= n; ++ i ) f[i][i][mp[s[i]]] = 1;
	
	for (int len = 2; len <= n; ++ len )
		for (int l = 1; l + len - 1 <= n; ++ l ) {
			int r = l + len - 1;
			for (int k = l; k < r; ++ k )
				for (int i = 0; i < 4; ++ i )
					if (f[l][k][i])
						for (int j = 0; j < 4; ++ j )
							if (f[k + 1][r][j])
								for (int c : pp[{i, j}])
									f[l][r][c] = 1;
		}
	
	bool flg = false;
	for (int i = 0; i < 4; ++ i )
		if (f[1][n][i])
			putchar(pm[i]),
			flg = true;
	if (!flg) puts("The name is wrong!");
}

\(\color{#52A41A}(7)\) P4170 [CQOI2007] 涂色

  • 有 \(n\) 个位置,最初均没有颜色。每次操作可以选择一个区间并覆盖同一种颜色。求最小操作次数使得与目标状态相同。
  • \(n \le 50\)。

设状态 \(f_{l, r}\) 表示将区间 \([l, r]\) 染成目标颜色的最少操作次数。

观察发现,如果我们想将一个区间 \([l, r]\) 全部染成目标颜色,那么第一步就可以将整个区间涂上同一种颜色。然后再慢慢调整。

同时,对于区间的左/右断点,显然如果将其染色大于 \(1\) 次显然不优。但对于中间位置不受影响。

所以我们可以在第一步就将整个区间涂成左端点的颜色。

此时,若左右端点颜色相同,我们可以染色区间 \([l, r - 1]\) 或 \([l + 1, r]\),然后在第一步染色时多染一格。

否则,枚举断点 \(k\),左右分别染色。

$\color{blue}\text{Code}$
int n;
char s[N];
int f[N][N];

void Luogu_UID_748509() {
	scanf("%s", s + 1);
	n = strlen(s + 1);
	memset(f, 0x3f, sizeof f);
	for (int i = 1; i <= n; ++ i ) {
		cin >> s[i];
		f[i][i] = 1;
	}
	for (int len = 2; len <= n; ++ len ) {
		for (int l = 1; l + len - 1 <= n; ++ l ) {
			int r = l + len - 1;
			if (s[l] == s[r]) f[l][r] = min(f[l][r - 1], f[l + 1][r]);
			else {
				for (int k = l; k < r; ++ k ) f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
			}
		}
	}
	
	fout << f[1][n];
}

\(\color{#BFBFBF}(8)\) LOJ P507 接竹竿

  • 有 \(n\) 张牌排成一排,每张牌有属性 \((c_i, v_i)\)。保证 \(c_i \le k\)。

    每次操作选择两张牌 \(l, r\) 满足 \(c_l = c_r\),删除 \(l \sim r\) 中的所有牌,并获得 \(\sum_{i=l}^rv_i\) 的收益。

    求最大的收益。

  • \(n, k \le 10^6\)。

设状态 \(f_i\) 表示若只考虑前 \(i\) 张牌,能获得的最大收益。

转移枚举第 \(i\) 张牌是否是在最后一次操作中被删,以及被哪个区间删。即 \(f_{i - 1}\) 和 \(\max_{j=1}^{i - 1}\{f_{j - 1} + \sum_{k=j}^iv_k \mid c_i = c_j\}\) 的较大值。

直接做是 \(n^3\) 的。区间求和那个部分可以前缀和优化,但仍然是 \(n^2\) 的,即 \(\max_{j=1}^{i - 1}\{f_{j - 1} + \sum_{k=1}^iv_k - \sum_{k=1}^{j-1}v_k \mid c_i = c_j\}\)。

可以把与 \(j\) 无关的提到外面,即 \(\sum_{k=1}^iv_k + \max_{j=1}^{i - 1}\{f_{j - 1} - \sum_{k=1}^{j-1}v_k \mid c_i = c_j\}\)。

然后这个就很好维护了。我们用桶维护每个 \(c_i\) 所对应的最大的 \(f_{i-1} - \sum_{k=1}^{i-1}v_k\),转移可以优化成 \(\Theta(1)\)。总时间复杂度 \(\Theta(n)\)。

$\color{blue}\text{Code}$
int n, k, res, c[N], v[N];
ll f[N], sum[N];
map<int, ll> mp;

void Luogu_UID_748509() {
	fin >> n >> k;
	for (int i = 1; i <= n; ++ i ) fin >> c[i];
	for (int i = 1; i <= n; ++ i ) fin >> v[i], sum[i] = sum[i - 1] + v[i];
	for (int i = 1; i <= n; ++ i ) {
		f[i] = f[i - 1];
		if (mp.count(c[i])) {
			f[i] = max(f[i], mp[c[i]] + sum[i]);
			mp[c[i]] = max(mp[c[i]], f[i - 1] - sum[i - 1]);
		}
		else {
			mp[c[i]] = f[i - 1] - sum[i - 1];
		}
		res = max(res, f[i]);
	}
	fout << res;
}

\(\color{#3498D8}(9)\) P4342 [IOI1998] Polygon

  • 有一个 \(n\) 个顶点 \(n\) 条边的环,顶点上有数字,边上有 \(+, \times\) 两种运算符号。

    首先删掉一条边,然后每次选择一条连接 \(V_1, V_2\) 的边,用边上的运算符计算 \(V_1\) 和 \(V_2\) 得到的结果来替换这两个顶点。

    求最后元素的最大值。

  • \(n \le 50\)。

显然区间 DP。首先倍长破环为链。

设状态 \(f_{l, r}\) 表示将区间 \(l \sim r\) 内的数字处理后得到的最大数字。转移枚举断点 \(k\),即 \(f_{l, r} = \max_{k=l}^{r-1} \operatorname{opt}(f_{l, k}, f_{k + 1, r})\),其中 \(\operatorname{opt}\) 表示边上的运算符号。

这样做是不正确的。注意到两个负数相乘结果为正数,所以再维护 \(g_{l, r}\) 表示最小值。转移类似。

复杂度 \(\Theta(n^3)\)。

$\color{blue}\text{Code}$
int n;
bool op[N];
int a[N];
int f[N][N], g[N][N];

void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i <= n; ++ i ) {
		char c;
		cin >> c >> a[i];
		op[i] = c == 'x';
		a[i + n] = a[i];
		op[i + n] = op[i]; 
	}
	
	for (int i = 1; i <= n * 2; ++ i ) {
		f[i][i] = g[i][i] = a[i];
	}
	
	for (int len = 2; len <= n; ++ len ) {
		for (int l = 1; l + len - 1 <= n * 2; ++ l ) {
			int r = l + len - 1;
			f[l][r] = -1e9, g[l][r] = 1e9;
			for (int k = l; k < r; ++ k ) {
				vector<int> v;
				if (op[k + 1]) v = {f[l][k] * f[k + 1][r], f[l][k] * g[k + 1][r], g[l][k] * f[k + 1][r], g[l][k] * g[k + 1][r]};
				else v = {f[l][k] + f[k + 1][r], f[l][k] + g[k + 1][r], g[l][k] + f[k + 1][r], g[l][k] + g[k + 1][r]};
				
				for (int i : v) {
					f[l][r] = max(f[l][r], i);
					g[l][r] = min(g[l][r], i);
				}
			}
		}
	}
	
	int res = -1e9;
	for (int l = 1, r = n; l <= n; ++ l, ++ r )
		res = max(res, f[l][r]);
	fout << res << '\n';
	
	for (int l = 1, r = n; l <= n; ++ l, ++ r )
		if (f[l][r] == res)
			fout << l << ' ';
}

\(\color{#52A41A}(10)\) P4933 大师

  • 给定一个长度为 \(n\) 的序列。求有多少个子序列是等差数列。
  • 设 \(v\) 为序列最大值。\(n \le 1000\),\(v \le 20000\)。

设状态 \(f_{i, j}\) 表示有多少个以 \(i\) 结尾的子序列是公差为 \(j\) 的等差数列,且长度大于 \(1\)。

转移枚举倒数第二个元素 \(k\),即 \(f_{i, j} = \sum_{k=1}^{i - 1} \{f_{k, j} + 1\mid a_i - a_k = j\}\)。其中 \(+1\) 的原因是我们可以选择长度为 \(1\) 的子序列。

复杂度是 \(\Theta(n^2v)\) 的。考虑优化。

可以发现如果确定了 \(a_i, j\) 那么 \(a_k\) 的值是可以确定的,即 \(a_k = a_i - j\)。所以处理方法与 (1) 类似,维护桶表示每一个 \(a_i\) 对应的 \(f_{i, j} + 1\) 之和。注意这里还有一个未处理的 \(j\),解决方法是将转移顺序改为先枚举 \(j\) 再枚举 \(i\)。这样在当前 \(j\) 这轮循环时就不需要考虑 \(j\) 的影响了。

时间复杂度 \(\Theta(nv)\)。

$\color{blue}\text{Code}$
int n, a[N];
int f[N][M * 2];
int res; 

int fpm(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		b >>= 1, a = (ll)a * a % P;
	}
	return res;
}

int mp[M * 5];
 
void Luogu_UID_748509() {
	fin >> n;
	int mx = -1e9, mn = 1e9;
	for (int i = 1; i <= n; ++ i ) fin >> a[i], mx = max(mx, a[i]), mn = min(mn, a[i]);
	
	register int res = n, m = mx - mn + 1;
	for (int j = -m; j <= m; ++ j ) {
		memset(mp, 0, sizeof mp);
		for (int i = 1; i <= n; ++ i ) {
			if (a[i] >= j) (f[i][j + M] = mp[a[i] - j]) %= P;
			(mp[a[i]] += f[i][j + M] + 1) %= P;
			res = (res + f[i][j + M]) % P;
		}
	}
	
	fout << res;
	return;
}

\(\color{#52A41A}(11)\) P5662 [CSP-J2019] 纪念品

  • 小伟突然获得一种超能力,他知道未来 \(T\) 天 \(N\) 种纪念品每天的价格。某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品换回的金币数量。

    每天,小伟可以进行以下两种交易无限次

    1. 任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品;
    2. 卖出持有的任意一个纪念品,以当日价格换回金币。

    每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。

    \(T\) 天之后,小伟的超能力消失。因此他一定会在第 \(T\) 天卖出所有纪念品换回金币。

    小伟现在有 \(M\) 枚金币,他想要在超能力消失后拥有尽可能多的金币。

  • \(T ,N \le 100\),\(M \le 1000\)。

一个观察,可以发现如果我持有一个物品多天(例如在 \(s\) 天买,\(t\) 天卖),相当于在 \(s + 1 \sim t - 1\) 这些天中,先将这个物品卖掉,再买回。

所以我们不需要记录每天手里持有多少纪念品,统一认为今天买的纪念品,明天就立刻卖掉。

设计 \(dp_{i, j, k}\) 表示第 \(i\) 天,考虑第 \(j\) 个物品,当前手中还有 \(k\) 元时,明天早上能获得的最大收益。则转移:

\[dp_{i, j, k} = \max(dp_{i, j - 1, k}, dp_{i, j - 1, k + p_{i, j}} + p_{i + 1, j}) \]

然后我们求出 \(dp_i\) 中的最大值,作为下一天的起始钱数(与 \(m\) 类似)。

$\color{blue}\text{Code}$
void Luogu_UID_748509() {
	int t, n, m;
	fin >> t >> n >> m;
	vector<vector<int> > p(t + 1, vector<int>(n + 1));
	for (int i = 1; i <= t; ++ i )
		for (int j = 1; j <= n; ++ j )
			fin >> p[i][j];
	vector<int> dp(10010);
	for (int i = 1; i < t; ++ i ) {
		fill(dp.begin(), dp.end(), 0);
		int tmp = m;
		for (int j = 1; j <= n; ++ j )
			for (int k = p[i][j]; k <= tmp; ++ k ) {
				dp[k] = max(dp[k], dp[k - p[i][j]] + p[i + 1][j]);
				m = max(m, dp[k] + tmp - k);
			}
	}
	fout << m;
	
}

标签:le,int,res,sum,笔记,color,区间,动态,规划
From: https://www.cnblogs.com/2huk/p/18194276

相关文章

  • RocSE论文阅读笔记
    TowardsRobustNeuralGraphCollaborativeFilteringviaStructureDenoisingandEmbeddingPerturbation论文阅读笔记Abstract​ 现有的鲁棒协同滤波工作主要通过去噪图结构来提高鲁棒性,而其他领域的最新进展表明,在嵌入空间中直接添加对抗性扰动可以显著提高模型的鲁棒性。......
  • 树做题笔记
    \(\color{#3498D8}(1)\)P4281[AHOI2008]紧急集合/聚会给定一棵\(n\)个节点的树。\(m\)次询问,每次给定\(a,b,c\),求一个节点\(u\)并使得三个点到\(u\)的距离和最小。求\(u\)和最小距离和。\(n,m\le5\times10^5\)。三个点\(a,b,c\)在树上的位置关系......
  • 初赛笔记
    第一章计算机基础知识1.计算机的概述计算机发展史第一代真空电子管;第二代晶体管;第三代集成电路;第四代大规模、超大规模集成电路 ;第五代智能计算机系统 第一台计算机1946年美国ENIAC 重要人物冯·诺伊曼 ,”计算机之父“,提出计算机体系结构图灵,"人......
  • 洛谷题单指南-动态规划3-P4342 [IOI1998] Polygon
    原题链接:https://www.luogu.com.cn/problem/P4342题意解读:环中节点表示数字,边表示运算符,可以任意断一条边,其余节点两两按边的符号计算,求结果的最大值,以及最大值是断开那些边可以得到。解题思路:题意中有几个个关键信息:环形,节点数为n,边数为n任意断一条边,即可以从任意节点开始,......
  • 树链剖分 学习笔记
    裂缝中的阳光-林俊杰头图有多少创伤卡在咽喉有多少眼泪滴湿枕头有多少你觉得不能够被懂的痛只能沉默有多少夜晚没有尽头有多少的寂寞无人诉说有多少次的梦还没做已成空等到黑夜翻面之后会是新的白昼等到海啸退去之后只是潮起潮落别到最后你才发觉心里头的......
  • RecDCL论文阅读笔记
    RecDCL:DualContrastiveLearningforRecommendation论文阅读笔记Abstract提出问题:​ 现有的基于cl的方法大多集中于批处理的对比,没有利用特征维度中潜在的规律性。这导致了在用户和项目的表示学习过程中的冗余解决方案。解决方法:​ 在这项工作中,我们研究了如何同时使用批......
  • 软件测评师笔记10--安全测试相关
    常见安全攻击手段1、冒充:一个实体假装成一个不同的实体,常和消息篡改和重演一起使用2、重演:当消息为了产生非授权效果而被重复时,就出现重演了3、消息篡改:数据所传送的内容被改变而未被发觉,并导致非授权后果4、服务拒绝:通过向认证/授权服务发送大量虚假请求,占用系统带宽造成关键......
  • 创建二维动态数组
    1//#include<bits/stdc++.h>2#include<iostream>3#include<vector>4usingnamespacestd;5intmain(){6intn;7cin>>n;8//writeyourcodehere......910////1.使用一维数组模拟11//int*num=......
  • 【vue3入门】-【22】 动态组件&组件保持存活&异步组件
    动态组件有些场景下会需要在两个组件之间来回切换,比如tab页面App.vue<template><!--组件标签--><component:is="tabComponent"></component><button@click="changeHandle">切换组件</button></template><script>importC......
  • 【论文笔记-44~】多语言实体链接
    ~20111.Cross-LanguageEntityLinking文章核心观点:本文介绍了一种新的跨语言实体链接任务,旨在将不同语言的文档中的命名实体与英文知识库中的实体描述进行匹配。作者提出了一种利用统计音译和跨语言信息检索的方法来解决这一任务,并在21种语言上进行了实验验证。实验结果显示,......