今日も、明日も、輝いている。|

Aquizahv

园龄:1年11个月粉丝:0关注:7

2025-02-02 18:30阅读: 4评论: 0推荐: 0

【学习笔记】DP套DP

前置知识:dp 是啥你总得知道吧()

dp 套 dp,又叫 dp of dp,此类题目一般以一个 dp 问题为方案,求方案数。比如求最长上升子序列长度为 x 的方案数。

BZOJ3864 Hero meet devil

(想不到吧 bzoj 登陆洛谷了)

那就以这个经典问题为例。

(一定要会 O(n2) 求 LCS!)

题意

给定一个字符集为 ACGT 的字符串 S。定义 LCS(S,T) 为两个字符串 S,T 的最长公共子序列。

对于每个 0i|S|,求有多少个长度为 m,字符集 ACGT 的字符串 T,满足 |LCS(S,T)|=i,答案对 109+7 取模。

1T51|S|151m1000

初步想法

题目要求方案数,尝试 dp。一种很 naive 的想法是,记录 S 遍历到 iT 遍历到 j,LCS 为 k 的方案数。这个显然不行啊,因为至少要选一个 i/j 重新遍历。

那既然每次都要重新更新一遍内层 dp 数组,我们把 dp 数组存一下不就好了,正好把 k 那维也省掉了。

思路

dpx,y 表示 T 中到xS 中到 yfi,S 表示,T 中遍历到 idpi|S| 个值。

但是 dpi 数组怎么状压?总不能按进制存吧。

观察一下,不难发现值单调不降,并且前后两项最多差 1。所以可以把它差分一下,变成一个二进制 mask

这样一来,枚举 i 以及 Ti,枚举 i1 轮的 mask,然后把 mask 前缀和一下,对它使用学过的 LCS 转移,最后差分回去为 mask。则 fi1,maskfi,mask 产生贡献。

最后统计答案部分我不说你也会。

问题就解决了。

但时间复杂度为 O(m×||×2|S|×|S|),有点悬啊!

优化

这时,再考虑进行一个很简单的优化:

注意到对于相同的参数 Timask,返回的 mask 一样。所以把所有的情况先预处理一遍,求 f 的时候就不用每次算了!

时间复杂度 O(||×2|S|+m×||×2|S|),毫无压力!

代码

#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 罢了。f 数组再记录一维表示 3 个阶段即可。

点击查看代码
#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 中国大陆许可协议进行许可。

posted @   Aquizahv  阅读(4)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起