[算法模版]子序列DP
[算法模版]子序列DP
如何求本质不同子序列个数?
朴素DP
复杂度为\(O(nq)\)。其中\(q\)为字符集大小。
\(dp[i]\)代表以第\(i\)个数结尾的本质不同子序列个数。注意,这里对于每一个字符,只计算上一个相同字符带来的贡献。如果全部计算的话会算重复。
最后统计答案的时候也只统计每个字符最后一次出现的位置的答案。
例题:【线上训练13】子序列 中的50分部分分
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=2e6+100,mod=998244353;
typedef long long ll;
char s[2][maxn],ori[maxn];
int n,len=1;
int last[30];
ll dp[maxn];
void init() {
scanf("%s",ori+1);
s[1][1]=ori[1];
for(int i=2;i<=n;i++) {
int md=i&1;
for(int j=1;j<=len;j++) {
s[md][2*j-1]=ori[i];
s[md][2*j]=s[md^1][j];
}
s[md][2*len+1]=ori[i];
len=2*len+1;
}
}
int main() {
scanf("%d",&n);
init();
scanf("%s",s[n&1]+1);len=strlen(s[n&1]+1);
for(int i=1;i<=len;i++) {
int ty=s[n&1][i]-'a';
dp[i]=1;
for(int j=0;j<26;j++) {
if(last[j]){
dp[i]+=dp[last[j]];
}
}
dp[i]%=mod;
last[ty]=i;
}
ll ans=0;
for(int i=0;i<26;i++) {
if(last[i])ans+=dp[last[i]];
ans%=mod;
}
ans%=mod;
printf("%lld",ans);
}
优化DP
我们令\(f[i]=\sum_{j=1}^q dp[last[c[j]]]\)。其中\(q\)为字符集大小,\(c\)字符集。
转移的时候有两种情况:
- 当前字符未出现过(第一个)。那么令\(dp[i]=f[i-1]+1\),则\(f[i]=2\times f[i-1]+1\)。
- 当前字符在原来出现过。那么令\(dp[i]=f[i-1]+1\)。因为只记录最后一次,所以在给\(f\)加上这一次的\(dp\)值之后还要删去上一次的\(dp\)值。\(f[i]=2\times f[i-1]-dp[last[i]]+1\)。因为\(dp[last[i]]=f[last[i]-1]+1\),所以\(f[i]=2\times f[i-1]-f[last[i]-1]\)
其实本质就是利用子序列DP每次增加的都是\(\sum dp[每一个字符]\)这个特性,每次转移将\(\sum dp[每一个字符]\)用\(O(1)\)复杂度起来,做到\(O(1)\)的转移。
算法复杂度\(O(n)\)
例题:FZU-2129
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int n,a[1000500],f[1000500],last[1000500],ans=0;
const int mod=1e9+7;
int main() {
ios::sync_with_stdio(0);
while(cin>>n) {
for(int i=1;i<=n;i++)cin>>a[i];ans=0;
memset(f,0,sizeof(f));memset(last,0,sizeof(last));
for(int i=1;i<=n;i++) {
if(!last[a[i]])f[i]=(2ll*f[i-1]+1)%mod;
else f[i]=(2ll*f[i-1]-f[last[a[i]]-1])%mod;
last[a[i]]=i;
}
ans=(f[n]%mod+mod)%mod;
cout<<ans<<endl;
}
return 0;
}
矩阵优化
我们可以发现,朴素DP的DP状态可以写成矩阵形式:
\\ dp[i][b]
\\ dp[i][c]
\\ ...
\\ ...
\\ ...
\\ dp[i][z]
\\1
\end{bmatrix}
\]
这里我们为了方便的进行矩阵转移添加了一维,\(dp[i][c]\)指对于前\(i\)个字符,最后一个字符是\(c\)的子序列个数。
那么字符串第\(i\)位的字符\(s[i]\)的状态可以这样转移:\(dp[i][s[i]]=\sum dp[i-1][任意字符]+1\)。
其他情况这样转移:\(dp[i][c]=dp[i-1][c]\)
我们发现这样的转移是可以构造出转移矩阵的。那么对于字符"c"的转移矩阵应该是这样的:
1&0 &0 &0 &0 &0 &0 &0\\
0&1 &0 &0 &0 &0 &0 &0\\
1&1 &1 &1 &1 &1 &1 &1\\
0&0 &0 &1 &0 &0 &0 &0\\
0&0 &0 &0 &1 &0 &0 &0\\
0&0 &0 &0 &0 &1 &0 &0\\
&&&...\\
0&0 &0 &0 &0 &0 &0 &1
\end{bmatrix}
\]
就是在单位矩阵上,将字符"c"对应的那一行(第三行)全部设置为1。
转移矩阵的最后一列比较特殊,我们使用最后一列来达到“+1”的效果。
构造出每个字符对应的转移矩阵后。我们每次只需要把状态矩阵乘上第\(i\)个字符\(s[i]\)的转移矩阵,就能得到新的转移矩阵。
普通情况下的合并子序列状态
上面这个矩阵最主要的作用就是拿来合并两个字符串的子序列状态。因为一个子序列状态是一个状态矩阵乘上所有转移矩阵的乘积。所以如果我们分别知道字符串\(s\)和\(t\)的转移矩阵乘积,我们就能轻松的得出\(st\)的转移矩阵乘积,从而得到字符串\(st\)的本质不同子序列个数。
但是这样显然是亏本的。一次合并的复杂度为\(O(len\times q^3)\)。其中\(q\)为字符集大小。直接\(O(n)\)DP一次不知道比它高到哪里去了。
特殊情况下的合并子序列状态
但是这种矩阵合并还是有一些用处的。当新的序列出现某种规律时(例如新串=旧串*2),我们就可以“重复使用”这个矩阵。显然是赚了的。

[算法模版]子序列DP的更多相关文章
- 网络流之最大流Dinic算法模版
/* 网络流之最大流Dinic算法模版 */ #include <cstring> #include <cstdio> #include <queue> using ...
- HDU 1231.最大连续子序列-dp+位置标记
最大连续子序列 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Sub ...
- [算法模版]Tarjan爷爷的几种图论算法
[算法模版]Tarjan爷爷的几种图论算法 前言 Tarjan爷爷发明了很多图论算法,这些图论算法有很多相似之处(其中一个就是我都不会).这里会对这三种算法进行简单介绍. 定义 强连通(strongl ...
- [算法模版]Prim-完全图最小生成树
[算法模版]Prim-完全图最小生成树 众所周知,对于常用的Kruskal算法,算法复杂度为\(O(m \log m)\).这在大多数场景下已经够用了.但是如果遇到及其稠密的完全图,Prim算法就能更 ...
- [算法模板]SOS DP
[算法模板]SOS DP 正文 SOS-DP(\(\text{Sum over Subsets}\))是用来解决这样的问题的: 其实就是子集和DP.上面每个\(F[mask]\)里面包含了\(mask ...
- [算法模版]AC自动机
[算法模版]AC自动机 基础内容 板子不再赘述,OI-WIKI有详细讲解. \(query\)函数则是遍历文本串的所有位置,在文本串的每个位置都沿着\(fail\)跳到根,将沿途所有元素答案++.意义 ...
- [算法模版]Link-Cut-Tree
[算法模版]Link-Cut-Tree 博主懒本博客只对现有博客进行补充,先直接放隔壁链接. FlashHu-LCT总结 Menci-LCT学习笔记 make-root操作 make-root操作用于 ...
- 模版 动态 dp
模版 动态 dp 终于来写这个东西了.. LG 模版:给定 n 个点的数,点有点权, $ m $ 次修改点权,求修改完后这个树的最大独立集大小. 我们先来考虑朴素的最大独立集的 dp \[dp[u][ ...
- 算法练习之DP 求LCM (最长公共子序列)
1. 对于序列x[1,i]和y[1,j],推导递推公式1.a 假设当前元素同样,那么就将当前最大同样数+12.b 假设当前元素不同.那么就把当前最大同样数"传递"下去 因此递推公式 ...
随机推荐
- [debug] 关闭vs的增量链接
1. 什么是增量链接? 答:采用Debug模式下,函数地址并不是该函数的开始部分,而是跳转到一个 jmp 函数地址. 比如,一个函数 test(),其地址 test 对应的汇编语句是 "jm ...
- MySQL的5大引擎及优劣之分
MySQL提供了多个不同的存储引擎,其中5大引擎有:innodb,myisam,memory,merge,csv. 其中优点有:灾难恢复性好.支持事务.使用行级锁.支持外键关联.支持热备份 缺点如下: ...
- PHP+Ajax点击加载更多列表数据实例
一款简单实用的PHP+Ajax点击加载更多列表数据实例,实现原理:通过“更多”按钮向服务端发送Ajax请求,PHP根据分页参数查询将最新的几条记录,数据以JSON形式返回,前台Query解析JSON数 ...
- django10-form表单组件
1.form组件的主要功能 生成页面的HTML标签和样式 ,将前端form表单的代码放在后端生成!! 对用户提交的数据进行校验(正则) 自动生成错误信息 保留上次输入信息 2.form组件常用字段与插 ...
- arcgis api 4.x for js 结合 react 入门开发系列react全家桶实现加载天地图(附源码下载)
基于两篇react+arcgis的文章介绍,相信大家也能体会两者的开发区别了.在“初探篇”中作者也讲述了自己的选择,故废话不多说,本篇带大家体验在@arcgis/webpack-plugin环境下,使 ...
- python 生成 树状结构
树状结构: 字典里只有一个键值对, key 为根, 值为一个列表, 列表里的某个或多个元素可以再进行分支(分支还是列表) 比如: 邮件的发件人, 收件人, 转发关系树状结构 forwarding_re ...
- MySQL事务和锁——《MySQL DBA工作笔记》
MySQL事务 事务存在的原因 事务存在的目的:保证用户对数据操作对数据是安全的.(比如说银行卡余额) 事务的特性--ACID 原子性:一个事务要么全部执行,要么不执行 一致性:事务开始和结束时,数据 ...
- bayaim_mysql_忘记密码 [仅限 5.6以下]
bayaim_mysql_忘记密码 [仅限 5.6以下] 原创 作者:bayaim 时间:2017-12-26 10:47:41 8 0删除编辑 忘记root密码------------------- ...
- [PHP] PHP PDO与mysql的连接单例防止超时情况处理
这个数据库类主要处理了单例模式下创建数据库对象时,如果有两次较长时间的间隔去执行sql操作,再次处理会出现连接失败的问题,利用一个cache数组存放pdo对象与时间戳,把两次执行之间的时间进行了比较, ...
- Linux 登录 MySQL 报错, 解决bash: mysql: command not found 的方法
原因:这是由于系统默认会查找/usr/bin下的命令,如果这个命令不在这个目录下,当然会找不到命令,我们需要做的就是映射一个链接到/usr/bin目录下,相当于建立一个链接文件. 首先得知道mysql ...