DP 套 DP(DP of DP)
把 DP 过程当作状态进行 DP。DP of DP 一般数据范围不会太大,而且一般是计数题。
DP of DP 的本质是自动机上 DP。 大致上可以写作
例一:Hero meet devil
题意:给出串
假设我们已经知道这是一道 DP 套 DP 的题目。那么对于 DP套DP,最重要的想法就是考虑内层 DP 是怎么进行的?
在这题,内层 DP 是 "最长公共子序列"。平时做 LCS,一般是
考虑完内层 DP,然后就要考虑怎么把内层 DP 的状态压缩入外层 DP 的状态。
因为
DP of DP 中,一般数据量较小的那一边作为被压缩的内层 DP。 比如这题,
但是我们面临第二个问题:怎么用
观察:当
证明:
按
若
若
若
证毕。
有了这个性质又怎么样?得出结论:
因此可以定义出
直接写包 TLE 的,需要预处理出
例二:BZOJ3591:最长上升子序列
题意:给出
在这道题目里,内层 DP 就是 "最长上升子序列"。
经过瞪眼法,我们发现
转念想到 LIS 的另一种求法:维护
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 16;
int n, m;
int a[N], id[N];
int pw[N];
int dp[15000000];
//dp[S]:有多少种a[1~i]的安排方式,使a[1~i]的表格G恰好为S
//i为S中非0个数
//0是没放,1是在序列内,2是已经出了
int getdig(int S, int x) {
for (int i = 1; i <= x; i++)
S /= 3;
return S % 3;
}
int ans = 0;
vector<int> g[3];
int main() {
cin >> n >> m;
pw[0] = 1;
for (int i = 1; i <= n; i++)
pw[i] = pw[i - 1] * 3;
for (int i = 1; i <= m; i++) {
cin >> a[i];
a[i]--;
id[a[i]] = i - 1;
}
for (int i = 1; i < m; i++)
if (a[i] > a[i + 1]) {
puts("0");
return 0;
}
dp[0] = 1;
int val[20], LIS[20];
for (int S = 0; S < pw[n]; S++)
if (dp[S]) {
int st = S, cnt = 0, cur = 0;
for (int i = 0; i < n; i++) {
val[i] = getdig(S, i);
if (val[i])
cnt++;
if (val[i] == 1)
LIS[cur++] = i;
}
if (cnt == n) {
ans += dp[S];
continue;
}
for (int i = 0; i < n; i++) {
if (val[i])
continue;
if (id[i] && !val[a[id[i]]])
continue;
int pos = 0;
while (LIS[pos] < i && pos < cur)
pos++;
if (pos == m)
continue;
int SS = S + pw[i];
if (pos < cur)
SS += pw[LIS[pos]];
dp[SS] += dp[S];
}
}
printf("%d\n", ans);
return 0;
}
例三:P8352:小 N 的独立集
题意:给一棵树每个结点赋值
DP 套 DP,第一步考虑最大独立集怎么求。
于是考虑
考虑优化。观察数据范围里的
所以改为记录
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5, MOD = 1e9 + 7;
int n, k;
vector<int> e[N];
int sz[N] = {};
int dp[N][N * 5][6]; //dp[i][j][k]:i子树内,f[i][0]=j,f[i][1]=k 的方案数
int tmp[N * 5][6];
void dfs(int x, int pr) {
sz[x] = 1;
for (int i = 1; i <= k; i++)
dp[x][0][i] = 1;
for (auto v: e[x]) {
if (v == pr)
continue;
dfs(v, x);
memset(tmp, 0, sizeof tmp);
for (int i = 0; i <= k * sz[x]; i++)
for (int j = 0; j <= k; j++) if (dp[x][i][j] != 0)
for (int p = 0; p <= k * sz[v]; p++)
for (int q = 0; q <= k; q++) if (dp[v][p][q] != 0) {
tmp[i + p + q][max(i + j + p, i + p + q) - (i + p + q)]
+= 1ll * dp[x][i][j] * dp[v][p][q] % MOD;
tmp[i + p + q][max(i + j + p, i + p + q) - (i + p + q)] %= MOD;
}
memcpy(dp[x], tmp, sizeof tmp);
sz[x] += sz[v];
}
}
int main() {
cin >> n >> k;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
for (int i = 1; i <= k * n; i++) {
int ans = 0;
for (int d = 0; d <= min(i, k); d++)
ans = (ans + dp[1][i - d][d]) % MOD;
cout << ans << endl;
}
return 0;
}
例四:CF979E:Kuro and Topological Parity
题意:初始有
这题
DP 套 DP,先考虑颜色定了,边定了怎么求黑白交替的路径条数。
然后考虑外层,
转移时枚举
这就是
如果写出了转移方程,就能发现(简记
白奇,若 ,无法从 转移而来;否则增加 。 白偶,若 ,增加 ;否则增加 。 黑奇,若 ,增加 ;否则增加 。 黑偶,若 ,无法从 转移而来;否则增加 。
因此只要记录
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 55, MOD = 1e9 + 7;
int n, p;
int clr[N];
ll dp[N][N][N][N];
//dp[i][a][b][c]:a黑偶,b黑奇,c白偶
ll pw[N];
int main() {
cin >> n >> p;
for (int i = 1; i <= n; i++)
cin >> clr[i];
pw[0] = 1;
for (int i = 1; i <= 50; i++)
pw[i] = pw[i - 1] * 2 % MOD;
memset(dp, 0, sizeof dp);
dp[0][0][0][0] = 1;
for (int i = 0; i < n; i++)
for (int a = 0; a <= i; a++)
for (int b = 0; b <= i - a; b++)
for (int c = 0; c <= i - a - b; c++) {
int d = i - a - b - c;
//考虑i+1是什么颜色
if (clr[i + 1] != 1) { //i+1黑色
//i+1黑偶
if (d == 0)
;
else
dp[i + 1][a + 1][b][c] += dp[i][a][b][c] * pw[i - 1] % MOD;
//i+1黑奇
if (d == 0)
dp[i + 1][a][b + 1][c] += dp[i][a][b][c] * pw[i] % MOD;
else
dp[i + 1][a][b + 1][c] += dp[i][a][b][c] * pw[i - 1] % MOD;
}
if (clr[i + 1] != 0) { //i+1白色
//若i+1白偶
if (b == 0)
;
else
dp[i + 1][a][b][c + 1] += dp[i][a][b][c] * pw[i - 1] % MOD;
//若i+1白奇
if (b == 0)
dp[i + 1][a][b][c] += dp[i][a][b][c] * pw[i] % MOD;
else
dp[i + 1][a][b][c] += dp[i][a][b][c] * pw[i - 1] % MOD;
}
dp[i + 1][a + 1][b][c] %= MOD;
dp[i + 1][a][b + 1][c] %= MOD;
dp[i + 1][a][b][c + 1] %= MOD;
}
ll ans = 0;
for (int a = 0; a <= n; a++)
for (int b = 0; b <= n - a; b++)
for (int c = 0; c <= n - a - b; c++) {
int d = n - a - b - c;
if ((b + d) % 2 == p)
ans = (ans + dp[n][a][b][c]) % MOD;
}
cout << ans << endl;
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战