【学习笔记】DP套DP
前置知识:dp 是啥你总得知道吧()
dp 套 dp,又叫 dp of dp,此类题目一般以一个 dp 问题为方案,求方案数。比如求最长上升子序列长度为
BZOJ3864 Hero meet devil
(想不到吧 bzoj 登陆洛谷了)
那就以这个经典问题为例。
(一定要会
题意
给定一个字符集为 ACGT
的字符串
对于每个 ACGT
的字符串
初步想法
题目要求方案数,尝试 dp。一种很 naive 的想法是,记录
那既然每次都要重新更新一遍内层 dp 数组,我们把 dp 数组存一下不就好了,正好把
思路
设
但是
观察一下,不难发现值单调不降,并且前后两项最多差
这样一来,枚举
最后统计答案部分我不说你也会。
问题就解决了。
但时间复杂度为
优化
这时,再考虑进行一个很简单的优化:
注意到对于相同的参数
时间复杂度
代码
#include <bits/stdc++.h> using namespace std; const int N = 1005, M = 20, MASK = (2 << 15) + 5, MOD = 1e9 + 7; char char_set[] = {"ACGT"}; vector<int> Mask[20]; int n, m, dp[2][M], trans[MASK][4], f[2][MASK], ans[M]; char s[M]; inline int madd(int x, int y) { return x + y >= MOD ? x + y - MOD : x + y; } inline int mmul(int x, int y) { return x * y % MOD; } inline void add(int &to, int from) { to = madd(to, from); } int DP(int mask, char c) { memset(dp, 0, sizeof(dp)); for (int i = 1; i <= m; i++) dp[0][i] = dp[0][i - 1] + ((mask >> (i - 1)) & 1); for (int i = 1; i <= m; i++) { dp[1][i] = max(dp[0][i], dp[1][i - 1]); if (c == s[i]) dp[1][i] = max(dp[1][i], dp[0][i - 1] + 1); } int res = 0; for (int i = 1; i <= m; i++) res += (dp[1][i] - dp[1][i - 1]) << (i - 1); return res; } void solve() { scanf("%s%d", s + 1, &n); m = strlen(s + 1); for (int mask = 0; mask < (1 << m); mask++) for (int k = 0; k < 4; k++) trans[mask][k] = DP(mask, char_set[k]); int cur = 0, lst = 1; memset(f, 0, sizeof(f)); f[cur][0] = 1; for (int i = 1; i <= n; i++) { swap(cur, lst); memset(f[cur], 0, sizeof(f[cur])); for (int k = 0; k < 4; k++) for (int mask = 0; mask < (1 << m); mask++) add(f[cur][trans[mask][k]], f[lst][mask]); } memset(ans, 0, sizeof(ans)); for (int mask = 0; mask < (1 << m); mask++) add(ans[__builtin_popcount(mask)], f[cur][mask]); for (int i = 0; i <= m; i++) printf("%d\n", ans[i]); } int main() { int T; cin >> T; while (T--) solve(); return 0; }
[TJOI2018] 游园会
一个很像的题,不过生成串中不能出现 NOI
罢了。
点击查看代码
#include <bits/stdc++.h> using namespace std; const int N = 1005, M = 20, MASK = (1 << 15) + 5, MOD = 1e9 + 7; char cset[] = {"NOI"}; int n, m, dp[2][M], trans[MASK][3], f[2][4][MASK], ans[M]; char s[M]; inline int madd(int x, int y) { return x + y >= MOD ? x + y - MOD : x + y; } inline void add(int &to, int fro) { to = madd(to, fro); } int DP(int mask, char c) { memset(dp, 0, sizeof(dp)); for (int i = 1; i <= m; i++) dp[0][i] = dp[0][i - 1] + ((mask >> (i - 1)) & 1); for (int i = 1; i <= m; i++) { dp[1][i] = max(dp[0][i], dp[1][i - 1]); if (c == s[i]) dp[1][i] = max(dp[1][i], dp[0][i - 1] + 1); } int res = 0; for (int i = 1; i <= m; i++) res += (dp[1][i] - dp[1][i - 1]) << (i - 1); return res; } int main() { cin >> n >> m; scanf("%s", s + 1); for (int mask = 0; mask < (1 << m); mask++) for (int k = 0; k < 3; k++) trans[mask][k] = DP(mask, cset[k]); int cur = 0, lst = 1; f[cur][0][0] = 1; for (int i = 1; i <= n; i++) { swap(cur, lst); memset(f[cur], 0, sizeof(f[cur])); for (int k = 0; k < 3; k++) { int to2 = k + 1, fro = k, to = k == 0 ? 1 : 0; // fro ? ->to2 : ->to for (int j = 0; j < 3; j++) for (int mask = 0; mask < (1 << m); mask++) add(f[cur][j == fro ? to2 : to][trans[mask][k]], f[lst][j][mask]); } } for (int j = 0; j < 3; j++) for (int mask = 0; mask < (1 << m); mask++) add(ans[__builtin_popcount(mask)], f[cur][j][mask]); for (int i = 0; i <= m; i++) printf("%d\n", ans[i]); return 0; }
[ZJOI2019] 麻将
学长推荐的 dp of dp 好题,我比较菜还没写(你不就是懒得写呗),读者可以自己尝试一下。
本文作者:Aquizahv's Blog
本文链接:https://www.cnblogs.com/aquizahv/p/18696973
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步