算法与数据结构 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;
}
posted @ 2026-08-04 21:27  cwkapn  阅读(4)  评论(0)    收藏  举报