题目链接

BZOJ 3864

题意简述

设字符集为ATCG,给出一个长为\(n(n \le 15)\)的字符串\(A\),问有多少长度为\(m(m \le 1000)\)的字符串\(B\)与\(A\)的最长公共子序列为\(i\),对所有\(0 \le i \le n\)输出答案。

题解

传说中的计算机理论科科科科科学家cls的DP套DP。

因为看别人写的题解我都看不懂……所以我在这篇题解中,换一种方式讲解,从暴力一点点优化得到DP套DP,应该更容易理解。

暴力怎么写呢?显然是枚举所有可能的字符串\(B\),然后对每一个都用经典的DP,求出与\(A\)的LCS。写个伪代码:

dfs(cur)
if(cur > m)
for(i: 1 -> m)
for(j: 1 -> n)
f[i][j] = max(f[i - 1][j], f[i][j - 1])
if(b[i] == a[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1)
ans[f[m][n]]++
return;
for(c in {A, T, C, G})
b[cur] = c
dfs(cur + 1)

考虑略微更改一下暴力的顺序,从把字符串枚举完再DP求LCS,变成一边枚举一边DP,并把\(f[cur]\)传入到递归函数中。

dfs(cur, f[])
if(cur > m)
ans[f[n]]++
return;
for(c in {A, T, C, G})
for(i : 1 -> n)
newf[i] = max(f[i], newf[i - 1])
if(c == a[i]) newf[i] = max(newf[i], f[i - 1] + 1)
dfs(cur + 1, newf)

往函数里传一个数组显然非常菜,考虑状压这个\(f\)数组。显然,一行f数组的每一位\(f[i]\)要么比\(f[i - 1]\)多1,要么和\(f[i - 1]\)相同。那么用一个长为\(n\)的二进制数状压这个\(f\)数组的差分即可。伪代码(\(cnt1(s)\)表示二进制数\(s\)中1的个数,此时就等于\(f[n]\)):

dfs(cur, s)
if(cur > m)
ans[cnt1(s)]++
return;
for(c in {A, T, C, G})
for(i : 1 -> n)
f[i] = f[i - 1] + (s >> (i - 1) & 1)
for(i : 1 -> n)
newf[i] = max(f[i], newf[i - 1])
if(c == a[i]) newf[i] = max(newf[i], f[i - 1] + 1)
for(i: 1 -> n)
t |= (f[i] - f[i - 1]) << (i - 1)
dfs(cur + 1, t)

\(s\)显然有很多重复的,每层DFS都这样算一遍非常浪费,因为这段代码中\(s\)对应的\(t\)只和\(c\)有关,不如预处理出每个\(s\)在\(B[cur] == c\)时能转移到哪个状态\(t\)(预处理方法就和上面这段代码中的那部分一样)。设这个状态\(t\)为\(trans[s][c]\)。

dfs(cur, s)
if(cur > m)
ans[cnt1(s)]++
return;
for(c in {A, T, C, G})
dfs(cur + 1, trans[s][c])

这个DFS都变成这样了,忍不住考虑能不能把它变成DP。用\(dp[i][s]\)表示字符串\(B\)长为\(i\),对应的数组\(f\)状压后为\(s\)的方案数。

dp[0][0] = 1
for(i : 1 -> m)
for(s: 1 -> (1 << n) - 1)
for(c in {A, T, C, G})
dp[i][trans[s][c]] += dp[i - 1][s]
for(s: 1 -> (1 << n) - 1)
ans[cnt1(s)] += dp[m][s]

至此你就从DFS暴力一步步优化出了这道题的DP套DP解法!

AC代码

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
}
template <class T>
void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
} const int N = 15, M = 1005, P = 1000000007;
int T, n, m, id[128], a[N];
int bcnt[1<<N], trans[1<<N][4], f[2][1<<N];
char str[N]; void init_trans(){
static int pre[N], cur[N];
for(int s = 0; s < (1 << n); s++){
if(s) bcnt[s] = bcnt[s >> 1] + (s & 1);
pre[0] = s & 1;
for(int i = 1; i < n; i++)
pre[i] = pre[i - 1] + (s >> i & 1);
for(int c = 0; c < 4; c++){
int t = 0;
cur[0] = pre[0];
if(c == a[0]) cur[0] = 1;
t |= cur[0];
for(int i = 1; i < n; i++){
cur[i] = max(cur[i - 1], pre[i]);
if(c == a[i]) cur[i] = max(cur[i], pre[i - 1] + 1);
t |= (cur[i] - cur[i - 1]) << i;
}
trans[s][c] = t;
}
}
}
void inc(int &x, int y){
x += y;
if(x >= P) x -= P;
}
void calc_f(){
int pre = 1, cur = 0;
memset(f[1], 0, sizeof(f[1]));
f[1][0] = 1;
for(int i = 0; i < m; i++){
for(int s = 0; s < (1 << n); s++)
f[cur][s] = 0;
for(int s = 0; s < (1 << n); s++)
if(f[pre][s]){
for(int c = 0; c < 4; c++)
inc(f[cur][trans[s][c]], f[pre][s]);
}
swap(pre, cur);
}
static int ans[N + 1];
for(int i = 0; i <= n; i++) ans[i] = 0;
for(int s = 0; s < (1 << n); s++)
inc(ans[bcnt[s]], f[pre][s]);
for(int i = 0; i <= n; i++)
write(ans[i]), enter;
} int main(){ id['A'] = 0, id['T'] = 1, id['C'] = 2, id['G'] = 3;
read(T);
while(T--){
scanf("%s%d", str, &m);
n = strlen(str);
for(int i = 0; i < n; i++)
a[i] = id[int(str[i])];
init_trans();
calc_f();
} return 0;
}

BZOJ 3864 Hero meet devil 超详细超好懂题解的更多相关文章

  1. bzoj 3864: Hero meet devil [dp套dp]

    3864: Hero meet devil 题意: 给你一个只由AGCT组成的字符串S (|S| ≤ 15),对于每个0 ≤ .. ≤ |S|,问 有多少个只由AGCT组成的长度为m(1 ≤ m ≤ ...

  2. bzoj 3864: Hero meet devil

    bzoj3864次元联通们 第一次写dp of dp (:з」∠) 不能再颓废啦 考虑最长匹配序列匹配书转移 由于dp[i][j]的转移可由上一行dp[i-1][j-1],dp[i-1][j],dp[ ...

  3. bzoj 3864: Hero meet devil(dp套dp)

    题面 给你一个只由\(AGCT\)组成的字符串\(S (|S| ≤ 15)\),对于每个\(0 ≤ .. ≤ |S|\),问 有多少个只由\(AGCT\)组成的长度为\(m(1 ≤ m ≤ 1000) ...

  4. BZOJ 3864 Hero meet devil (状压DP)

    最近写状压写的有点多,什么LIS,LCSLIS,LCSLIS,LCS全都用状压写了-这道题就是一道状压LCSLCSLCS 题意 给出一个长度为n(n<=15)n(n<=15)n(n< ...

  5. BZOJ 3864 Hero Meets Devil

    题目大意 给定一个由AGCT组成的串\(t\), 求对于所有的\(L \in [1, |t|]\), 有多少个由AGCT组成的串\(s\)满足\(LCS(s, t) = L\). Solution 传 ...

  6. 【BZOJ3864】Hero meet devil DP套DP

    [BZOJ3864]Hero meet devil Description There is an old country and the king fell in love with a devil ...

  7. bzoj千题计划241:bzoj3864: Hero meet devil

    http://www.lydsy.com/JudgeOnline/problem.php?id=3864 题意: 给你一个DNA序列,求有多少个长度为m的DNA序列和给定序列的LCS为0,1,2... ...

  8. HDU 4899 Hero meet devil(状压DP)(2014 Multi-University Training Contest 4)

    Problem Description There is an old country and the king fell in love with a devil. The devil always ...

  9. bzoj3864: Hero meet devil

    Description There is an old country and the king fell in love with a devil. The devil always asks th ...

随机推荐

  1. Sql 截取字段中的字符串

    取 a 字段里有字符x后面的数 right(a, charindex('x',reverse(a))-1))      reverse(字段) 这个函数是把字段倒过来并转换成nvarchar类型 取 ...

  2. Python 学习 第四篇:动态类型模型

    Python的变量不用声明,赋值之后就可以直接使用,类型是在运行过程中自动确定的,这就是动态类型模型.该模型把变量和对象设计成两个不同的实体,对象是存储数据的地方,对象的类型是由初始值自动决定的,而变 ...

  3. for循环两个略骚的写法

    骚写法 或许你知道,总之我觉得很酷,希望你也这么认为. 递增遍历 最常见场景,从 0 到 10 的遍历,不输出 10: for(let i = -1; ++i < 10;) { console. ...

  4. 该如何以正确的姿势插入SVG Sprites?

    大家好,我是苏南,今天要给大家分享的是SVG sprite(也叫雪碧图),所谓雪碧图,当然就不是我们常喝的雪碧饮料(Sprites)哦,哈哈- 当下流程的移动端,手机型号太多太多,今天工作项目中突然发 ...

  5. 手机APP自动化之uiautomator2 +python3 UI自动化

    题记: 之前一直用APPium直到用安卓9.0  发现uiautomatorviewer不支持安卓 9.0,点击截屏按钮 一直报错,百度很久解决方法都不可以,偶然间看见有人推荐:uiautomator ...

  6. Steamworks上传游戏

    1.在steamPipe下配置Depot,每个Depot表示程序对应的分支配置语言,操作系统,架构组合等 2.安装,启动项目是配置游戏启动文件的相关信息,不同的操作系统架构等需要添加不同的启动项 3. ...

  7. kvm虚拟化管理平台WebVirtMgr部署-完整记录(1)

    公司机房有一台2U的服务器(64G内存,32核),由于近期新增业务比较多,测试机也要新增,服务器资源十分有限.所以打算在这台2U服务器上部署kvm虚拟化,虚出多台VM出来,以应对新的测试需求.当KVM ...

  8. Visual Studio2013的安装过程及练习测试

    一.安装环境: 支持安装的操作系统版本:Windows XP,Windows7,Windows8,Windows10. CPU大小:Intel(R)Core(TM)i5-4210U CPU @1.7G ...

  9. linux第二次读书笔记

    <Linux内核设计与实现>读书笔记 第五章 系统调用   第五章系统调用 系统调用是用户进程与内核进行交互的接口.为了保护系统稳定可靠,避免应用程序恣意忘形. 5.1与内核通信 系统调用 ...

  10. 软件工程(四)数据流图DFD

    结构化分析中,常用到数据模型为实体关系图,功能模型是数据流图 DFD 可以认为,一个基于计算机的信息处理系统由数据流和一系列的转换构成,这些转换将输入数据流变换为输出数据流.数据流图就是用来刻画数据流 ...