【练习】DP 做题记录
CF1714D——Color with Occurrences
Description
给定一个字符串 \(t\) 和 \(n\) 个模板串 \(s_i\),若 \(s_i\) 和 \(t\) 的某个子串 \(t[l\ldots r]\) 相等,则可以花费 \(1\) 的代价将这个子串染色,同一段可以被染色多次。求最小代价和方案,或者输出无解。
Solution
由于同一段可以被染色多次,所以最小代价和顺序无关,由于 \(1 \leq \vert t \vert \leq 100\),\(1 \leq \vert s_i \vert \leq 100\) 而且 \(1 \leq n \leq 10\) 可以考虑 \(dp\)。我们可以定义状态 \(dp_{i, j}\) 为将 \(t\) 的前 \(i\) 个字符都染色的最小代价。接下来考虑转移,为了提高效率,我们可以先预处理出 \(len_j\) 表示 \(s_j\) 的长度, \(flag_{i, j}\) 表示 \(s_j\) 能否匹配字符串 \(t\) 中以位置 \(i\) 结尾,长度为 \(len_{j}\) 的子串。这样就可以较轻松地写出状态转移方程,对于每个位置 \(i\),考虑每个能够匹配的\(s_j\),对于每个 \(s_j\),从 \(i - len_j \to i\) 区间内转移。所以状态转移方程如下:
\(dp_{i, j} = \min_{flag_{i, j}=1}\){\(\min_{i - len_j} ^ i dp_k + 1\)}
而记录答案就用 \(pre\) 数组和 \(ans\) 数组记录即可。
Code
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <iostream>
using namespace std;
const int MAXN = 105;
int T;
int n, m;
string s;
int dp[MAXN], pre[MAXN];
int flag[MAXN][MAXN];
int len[MAXN], ans[MAXN];
void print(int k) {
if (!k)
return;
print(pre[k]);
printf("%d %d\n", ans[k], k - len[ans[k]] + 1);
return;
}
int main() {
scanf("%d", &T);
while (T--) {
cin >> s;
n = s.length();
s = '0' + s;
memset(dp, 0x3f, sizeof(dp));
memset(pre, 0, sizeof(pre));
memset(flag, 0, sizeof(flag));
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
string t;
cin >> t;
len[i] = t.length();
for (int j = len[i]; j <= n; j++)
if (s.substr(j - len[i] + 1, len[i]) == t)
flag[j][i] = 1;
}
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!flag[i][j])
continue;
for (int k = i - len[j]; k < i; k++) {
if (dp[k] + 1 < dp[i]) {
pre[i] = k;
ans[i] = j;
dp[i] = dp[k] + 1;
}
}
}
}
if (dp[n] == 0x3f3f3f3f)
printf("-1\n");
else {
printf("%d\n", dp[n]);
print(n);
}
}
return 0;
}
CF1675G——Sorting Pancakes
Description
有 \(n\) 个盘子和 \(m\) 个馅饼,初始时第 \(i\) 个箱子有 \(a_i\) 个馅饼。每次操作可以将一个馅饼移到相邻的箱子里。
求要使得最终数组中任意一个 \(i\) 满足 \(a_i \geq a_{i + 1}\)。
Solution
我们可以定义状态 \(dp_{i, j, k}\) 表示前 \(i\) 盘放 \(j\) 张馅饼,第 \(i\) 盘放 \(k\) 张馅饼,如果我们规定一个盘子只能向右边一个盘子拿或放馅饼。不难发现 \(dp_{i, j, k}\) 可以转移到 \(dp{i + 1, j + p, p}\)。
我们可以定义一个前缀和数组 \(S_i\)。
我们可以开始考虑状态转移方程:因为我们转移时,只和右边一个盘子有关,所以前 \(i\) 个的转移的状态中,前 \(i + 1\) 个盘子上的馅饼数就是 \(S_{i + 1}\)。
所以我们只需从后面一个盘子中去 \(j - p + S_{i + 1}\) 个馅饼即可。
但是我们的时间复杂度为 \(O(n \times m ^ 3)\),这肯定是不行的,考虑优化。
由于 \(f_{i + 1, j + p, p}\) 会被搜有的 \(f_{i, j, k}\) 转移,所以,考虑倒着枚举 \(k\)。
设 \(minv = \min_{u = k} ^ m f_{i, j, u}\),此时的状态转移方程就简化为:
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 255, INF = 1e9;
int n, m, ans;
int a[MAXN], sum[MAXN];
int dp[MAXN][MAXN][MAXN];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), sum[i] = sum[i - 1] + a[i];
memset(dp, 0x3f, sizeof(dp));
dp[0][0][m] = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j <= m; j++) {
int minv = INF;
for (int k = m; k >= 0; k--) {
minv = min(minv, dp[i][j][k]);
if (j + k <= m)
dp[i + 1][j + k][k] = min(dp[i + 1][j + k][k], minv + abs(j + k - sum[i + 1]));
}
}
ans = INF;
for (int i = 0; i <= m; i++)
ans = min(ans, dp[n][m][i]);
printf("%d", ans);
return 0;
}
CF1625C——Road Optimization
Description
Solution
Code
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 505, INF = 1e9;
int n, l, k;
int d[MAXN], a[MAXN];
int dp[MAXN][MAXN];
int main() {
scanf("%d %d %d", &n, &l, &k);
for (int i = 1; i <= n; i++)
scanf("%d", &d[i]);
d[n + 1] = l;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
memset(dp, 0x3f, sizeof(dp));
for (int i = 0; i <= k; i++)
dp[1][i] = 0;
for (int i = 2; i <= n + 1; i++)
for (int j = 0; j <= min(i - 1, k); j++)
for (int u = 0; u <= j; u++)
dp[i][j] = min(dp[i][j], dp[i - u - 1][j - u] + (d[i] - d[i - u - 1]) * a[i - u - 1]);
int ans = INF;
for (int i = 0; i <= k; i++)
ans = min(ans, dp[n + 1][i]);
printf("%d", ans);
return 0;
}
本文来自博客园,作者:zhou_ziyi,转载请注明原文链接:https://www.cnblogs.com/zhouziyi/p/16588224.html

浙公网安备 33010602011771号