算法与数据结构 10 - DP 套 DP
概述
DP 套 DP 是一种特殊的 DP,一般用于解决这类问题:给出一个能用传统 DP 解决的问题,然后问这个问题的答案为 \(x\) 的方案数。这种 DP 会把一个 DP 的信息用作另一个 DP 的状态来求解问题。博客编写时大量参考了 OI Wiki 的相关页面。
引入
例题:BZOJ3864 Hero meet devil(Luogu)
\(X\) 组询问。给定一个字符集为
ACGT的字符串 \(S\)。定义 \(\text{LCS}(S,T)\) 为两个字符串 \(S,T\) 的最长公共子序列。
对于每个 \(0\leq i \leq |S|\),求有多少个长度为 \(m\),字符集ACGT的字符串 \(T\),满足 \(|\text{LCS}(S,T)|=i\),答案对 \(10^9+7\) 取模。
保证 \(1\leq X\leq 5\),\(1\leq |S| \leq 15\),\(1\leq m\leq 1000\)。
可以发现,求两字符串的 LCS 是能用 DP 解决的问题,但此问题要求程序计算出 LCS 为 \(i\) 的字符串数量。这满足上面提到的问题特征。
先回顾求 LCS 的做法:设 \(g_{i,j}\) 表示 \(S\) 长度为 \(i\) 的前缀和 \(T\) 长度为 \(j\) 的前缀的 LCS,则有 \(g_{i,j}\leftarrow\max\{g_{i-1,j},g_{i,j-1},g_{i-1,j-1}+[S_i=T_j]\}\)。
于是设 \(f_{i,j}\) 表示对于长度为 \(i\) 的,与 \(S\) 求 LCS 的 \(g\) 数组状压后为 \(j\) 的字符串数量。由于 \(g_{i,j}-g_{i,j-1}\in\{0,1\}\),所以总状态数仅为 \(\mathcal{O}(2^{|S|})\)。现在考虑如何从 \(f_{i,j}\) 转移。如果知道了 \(T_{i+1}\) 和 \(j\),那么就能知道转移后的 \(j'\)。因此可以枚举 \(T_{i+1}\) 的值,然后令 \(f_{i+1,j'}\xleftarrow+ f_{i,j}\) 即可。
参考代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define gc getchar_unlocked
template<typename T> void read(T &x) {
char c = gc(); int f = 1; x = 0;
while (!isdigit(c)) f = (c == '-' ? -f : f), c = gc();
while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = gc();
x *= f;
}
template<typename T, typename ... Args> void read(T &x, Args& ... args) {read(x); read(args...);}
#define rep(i, s, t) for(int i=(s);i<=(t);i++)
#define per(i, s, t) for(int i=(s);i>=(t);i--)
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define lll __int128
const int N = 1005, mod = 1e9 + 7;
template<typename T> void chkmx(T &x, const T &y) {x = max(x, y);}
template<typename T> void chkmn(T &x, const T &y) {x = min(x, y);}
template<typename T> void chksum(T &x, const T &y) {x = (x + y) % mod;}
char S[16];
int n, m, lim, f[N][1 << 16], ans[16], nxt[1 << 16][4], g[N], h[N];
inline int trans(char c) {
switch (c) {
case 'A': return 0; break;
case 'T': return 1; break;
case 'C': return 2; break;
case 'G': return 3; break;
default: return -1;
}
}
int mch(int x, int y) {
rep(i, 0, n - 1) g[i + 1] = g[i] + ((x >> i) & 1);
rep(i, 1, n) {
if (trans(S[i]) == y) h[i] = g[i - 1] + 1;
else h[i] = max(h[i - 1], g[i]);
}
int res = 0;
rep(i, 1, n) if (h[i] > h[i - 1]) res |= (1 << (i - 1));
return res;
}
void pd() {
n = 0, m = 0;
char _ = gc();
while (!isalpha(_)) _ = gc();
while (isalpha(_)) S[++n] = _, _ = gc();
read(m); lim = 1 << n;
memset(f, 0, sizeof(f));
memset(ans, 0, sizeof(ans));
rep(i, 0, lim - 1) rep(j, 0, 3) nxt[i][j] = mch(i, j);
f[0][0] = 1;
rep(i, 0, m - 1) {
rep(j, 0, lim - 1) {
if (f[i][j])
rep(k, 0, 3) {
chksum(f[i + 1][nxt[j][k]], f[i][j]);
}
}
}
rep(i, 0, lim - 1) chksum(ans[__builtin_popcount(i)], f[m][i]);
rep(i, 0, n) printf("%d\n", ans[i]);
}
int main() {
int _; read(_);
while (_--) pd();
return 0;
}
本文来自博客园,作者:cwkapn,转载请注明原文链接:https://www.cnblogs.com/cwkapn/p/19213788


浙公网安备 33010602011771号