[题解]P2516 [HAOI2010] 最长公共子序列——求LCS个数
总的来说,这道题确实很精妙,难度也不小,耗费了不少时间去调。本来想过用容斥的思想,却因为当时理解不深没有继续思考就放弃了。学会了思路后对\(LCS\)有了更深层次的理解。
题意简述
给定\(A,B\)两个字符串(以.结尾),求它们的最长公共子序列的长度和个数(取模\(10^8\))。
注意:两个字符串可能有多个最长公共子序列,同一个最长公共子序列,哪怕放的位置不同,也算作两个。
样例
输入:
ABCBDAB.
BACBBD.
输出:
4
7
样例解释
长度为\(4\):ABBD\(*1\),ACBB\(*1\),ACBD\(*2\),BCBB\(*1\),BCBD\(*2\)。
思路简述
根据\(LCS\)算法的思想,我们画出以下表格(注意路径不唯一,所以每个格子不止一个箭头)。右下角的暗红色数字是\(f\)数组,记录长度。

第一问就是\(f[n][m]\)的值(\(n,m\)表示\(A,B\)的长度)。
第二问怎么解决呢?结果显然就是从\((n,m)\)开始,根据格子上的指示回溯的路径条数。当时以为这里用记忆化搜索直接计数就能得到正确答案,但其实这样做可能会有重复。如下图:

从右下开始走,左-上-左上和上-左-左上的效果是一样的,都是选择了C加入子序列。而左上-左上的效果不同,选择了D和C加入子序列。由此可以看出并不是路径不同最终子序列就不同。应该是路径中经过的位置不同,最终子序列就不同(注意不同的子序列可能字符串表示相同,前面题意简述说明了)。
所以搜索的方法会有重复计算的错误,考虑和\(f\)一样,定义一个\(g\)数组,\(g[i][j]\)表示\(A,B\)长度分别为\(i,j\)的前缀\(LCS\)的个数。
如果\(a[i]=b[j]\),说明此处有一个
,那么应该有这样子\(4\)种情况:

也就是说,如果\(a[i]=b[j]\),首先\(g[i][j]+=g[i-1][j-1]\),表示从这个格子往走的个数(长度为\(3+1=4\)),如果\(f[i-1][j]=f[i][j]\)或者\(f[i][j-1]=f[i][j]\),说明我们也可以不选,而是选择←或↑,仍然能选\(4\)个。如果\(a[i]\neq b[j]\),那么此处没有
。判断方法相同:- 如果\(f[i-1][j]=f[i][j]\)说明有
↑,\(g[i][j]+=g[i-1][j]\); - 如果\(f[i][j-1]=f[i][j]\)说明有
←,\(g[i][j]+=g[i][j-1]\)。
但是如果\(f[i-1][j-1]=f[i][j]\)(\(a[i]\neq b[j]\)不代表\(f[i-1][j-1]\neq f[i][j]\)),说明
左-和上-的操作可能有重复计算(就是第二张图那种,左-上和上-左都能到达\((i-1,j-1)\),又因为\(f[i-1][j-1]=f[i][j]\),所以都是最长。这样就重复计算了),需要\(g[i][j]-=g[i-1][j-1]\)。- 如果\(f[i-1][j]=f[i][j]\)说明有
综上,我们得到状态转移方程:
\(f[i][j]=
\begin{cases}
f[i-1][j-1]+1 & if\ a[i]=b[j]\\
max(f[i-1][j],f[i][j-1]) & otherwise
\end{cases}\)
\(g[i][j]
\begin{cases}
=1 & if\ i=0\ or\ j=0\\
otherwise:\\
+=g[i-1][j-1] & if\ a[i]=b[j]\\
+=g[i-1][j] & if\ f[i-1][j]=f[i][j]\\
+=g[i][j-1] & if\ f[i][j-1]=f[i][j]\\
-=g[i-1][j-1] & if\ f[i-1][j-1]=f[i][j]\\
\end{cases}\)
注意事项
- 别忘取模\(10^8\)。
- 开\(5000*5000\)会炸空间,所以需要滚动数组优化空间。
- 虽然递推有减法,但我们不需要担心出现负数影响取模。因为我们一定会作加法,而且加的数所在位置一定不在减的数所在位置左或上(还是比较容易理解的)。
具体见代码~
Code
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mod 100000000ll
using namespace std;
string a,b;
int n,m,f[5010][5010];
int g[5010][5010];
signed main(){
cin>>a>>b;
n=a.size()-1,m=b.size()-1;
a=' '+a,b=' '+b;
bool cur=1,pre=0;
for(int i=0;i<=m;i++) g[0][i]=1;
g[1][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
g[cur][j]=0;
if(a[i]==b[j]) f[cur][j]=f[pre][j-1]+1;
else f[cur][j]=max(f[pre][j],f[cur][j-1]);
if(a[i]==b[j]) g[cur][j]+=g[pre][j-1];
if(f[cur][j]==f[cur][j-1]) g[cur][j]+=g[cur][j-1];
if(f[cur][j]==f[pre][j]) g[cur][j]+=g[pre][j];
if(f[cur][j]==f[pre][j-1]) g[cur][j]-=g[pre][j-1];
g[cur][j]%=mod;
}
cur=!cur,pre=!pre;
}
cout<<f[pre][m]<<endl<<g[pre][m];
return 0;
}
\]
欢迎在评论区留下你的问题或建议。
[题解]P2516 [HAOI2010] 最长公共子序列——求LCS个数的更多相关文章
- 洛谷P2516 [HAOI2010]最长公共子序列(LCS,最短路)
洛谷题目传送门 一进来就看到一个多月前秒了此题的ysn和YCB%%% 最长公共子序列的\(O(n^2)\)的求解,Dalao们想必都很熟悉了吧!不过蒟蒻突然发现,用网格图貌似可以很轻松地理解这个东东? ...
- 2021.12.10 P2516 [HAOI2010]最长公共子序列(动态规划+滚动数组)
2021.12.10 P2516 [HAOI2010]最长公共子序列(动态规划+滚动数组) https://www.luogu.com.cn/problem/P2516 题意: 给定字符串 \(S\) ...
- P2516 [HAOI2010]最长公共子序列 题解(LCS)
题目链接 最长公共子序列 解题思路 第一思路: 1.用\(length[i][j]\)表示\(a\)串的前\(i\)个字符与\(b\)串的前\(j\)个字符重叠的最长子串长度 2.用\(num[i][ ...
- 洛谷 P2516 [HAOI2010]最长公共子序列
题目传送门 解题思路: 第一问要求最长公共子序列,直接套模板就好了. 第二问要求数量,ans[i][j]表示第一个字符串前i个字符,第二个字符串前j个字符的最长公共子序列的数量 如果f[i][j]是由 ...
- [HAOI2010]最长公共子序列(LCS+dp计数)
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X ...
- luogu P2516 [HAOI2010]最长公共子序列
传送门 首先那个\(O(n^2)\)的dp都会吧,不会自己找博客或者问别人,或是去做模板题(误) 对以下内容不理解的,强势推荐flash的博客 我们除了原来记录最长上升子序列的\(f_{i,j}\), ...
- 洛谷P2516 [HAOI2010]最长公共子序列
题目描述 字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X="x0,x1,-,xm-1",序列Y=& ...
- Luogu P2516 [HAOI2010]最长公共子序列 DP
首先$LIS$显然:$f[i][j]=max(f[i][j-1],f[i-1][j],(a[i]==b[j])*f[i-1][j-1])$ 考虑如何转移数量: 首先,不管$a[i]$是否等于$b[j] ...
- P2516 [HAOI2010]最长公共子序列
传送门 看到数据范围,显然 $n^2$ 的 $dp$... 设 $f[i][j]$ 表示 $A$ 串考虑了前 $i$ 位,$B$ 串考虑了前 $j$ 位,最优情况下的方案数 但是好像没法判断转移来的是 ...
- [BZOJ2423][HAOI2010]最长公共子序列
[BZOJ2423][HAOI2010]最长公共子序列 试题描述 字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X=“x ...
随机推荐
- 代码随想录第一天|数组part01
二分查找 题目建议: 大家今天能把 704.二分查找 彻底掌握就可以,至于 35.搜索插入位置 和 34. 在排序数组中查找元素的第一个和最后一个位置 ,如果有时间就去看一下,没时间可以先不看,二刷的 ...
- linux系统僵尸进程处理
查看僵尸进程 [root@mac-25 ~]# top top - 19:04:11 up 177 days, 23:58, 2 users, load average: 15.18, 21.64, ...
- LinkedList链表
LinkedList 他是继承的List 双向链表 每当我们new一个linklist对象的时候 LinkedList linkedList = new LinkedList(); 他会先创建一个Li ...
- Scratch之Android的Animation动画的四种动画效果——透明度渐变动画
废话不多说,先上图为敬 效果演示 编写的程序展示 讲话开始: 怎么想到的 在平时上课的时候,有一个学生拿着他好基友写的游戏程序给我看,最开始写的原型是叫虚像的积木块程序.通过一个局部变量开关控制虚像是 ...
- 实战干货|Spark 在袋鼠云数栈的深度探索与实践
Spark 是一个快速.通用.可扩展的大数据计算引擎,具有高性能.易用.容错.可以与 Hadoop 生态无缝集成.社区活跃度高等优点.在实际使用中,具有广泛的应用场景: · 数据清洗和预处理:在大数据 ...
- 你应该懂的AI大模型(十)之 LLamaFactory 之 LoRA微调Llama3
本文标题中说的微调 Llama3指的是局部微调,使用 LLamaFactory 局部微调 LIama3. 一.什么是LLamaFactory LLaMA-Factory 是一个开源的大型语言模型微调框 ...
- 层次分析法的Python实现--数学建模学习日志
数学建模比赛即将到来,大家应该都投身于学习当中了,b站上比较热门的一个课程是 b站数学建模学习视频 在这里讲解了数学建模常见的方法和写论文的要点,同时up主贴心地有所有方法的源码实现,但是全部都是ma ...
- HashMap集合--基本操作流程的源码可视化
本文主要包含:HashMap 插入过程.扩容过程.查询过程和删除过程的源码可视化 文章对应的视频连接:https://www.bilibili.com/video/BV1wM3KzaE3d/ 1. 操 ...
- websocket拦截器,统一处理参数和返回值json数据
前言 传统的ws,发送前后都需要json序列化和反序列化这对编写代码并不友好.所以我做了个优化 废话不多说,上代码 my-ws.js const ws = new WebSocket("ws ...
- 企业如何通过ETL工具实现主数据的同步
1. 主数据的定义与重要性 主数据,作为企业的核心数据资产,涵盖了客户.产品.供应商.员工等关键业务实体信息.这些数据的稳定性.共享性和对决策的影响力,使其成为企业运营和战略决策不可或缺的基础.主数据 ...