首页 > 其他分享 >P1819 公共子序列 | P3856 [TJOI2008]公共子串

P1819 公共子序列 | P3856 [TJOI2008]公共子串

时间:2023-05-08 13:34:22浏览次数:39  
标签:P3856 27 const int 序列 build 公共 TJOI2008

简要题意

给出三个由小写英文字母组成的字符串 \(A,B,C\)。求这三个字符串的本质不同公共子序列个数。

  • P1819:\(n=|A|=|B|=|C|,1 \leq n \leq 150\),答案对 \(10^8\) 取模。
  • P3856:\(1 \leq |A|,|B|,|C| \leq 100\)。

思路

对于子序列问题,我们先建出子序列自动机。这里简单介绍一下子序列自动机是什么:

对于一个字符串 \(S\),令 \(F(i,j)\) 为 \([i+1,|S|]\) 中最小的 \(k\),使得 \(S_k=j\)。则 \(F(i,j)\) 就是我们要求的子序列自动机。

\(F(i,j)\) 有一个 \(O(|S|W)\) 的做法(\(W\) 为 \(S\) 中字符集大小)。我们逆推,不难发现以下结论:

\[F(i,j)=\begin{cases} 0&i=|S|\\ i+1&(j=S_{i+1})\\ F(i+1,j)&(j\neq S_{i+1}) \end{cases} \]

注意 \(i\) 可以为 \(0\)。此时指向的是最前的为 \(j\) 的位置。可以在字符串开头字符不同时充当相同的开头。

然后就可以 \(O(|S|W)\) 递推了。

然后我们回到本题。考虑 dp,令 \(f(i,j,k)\) 表示考虑到 \(A[i:|A|],B[j:|B|],C[k:|C|]\) 的公共子序列数。那么答案就是 \(f(0,0,0)\)。

考虑如何转移。如果 \(i,j,k\) 后继都有一个相同的字符(至于如何判断相同的字符,可以构造 \(A,B,C\) 的子序列自动机 \(F,G,H\)),那么就是一个公共子序列,可以转移。最后自己本身也是一个子序列(注意当 \(i,j,k\) 为 \(0\) 时这不是一个子序列)也就是:

\[f_{i,j,k}=[ijk \neq 0]+\sum_{s=\texttt{a}}^{\texttt{z}}[F(i,s)G(i,s)K(i,s) \neq 0]f(F(i,s),G(i,s),K(i,s)) \]

然后这道题就做完了。

代码

P1819:

#include <bits/stdc++.h>
using namespace std;

const int N = 155;
int n;
string a,b,c;
int f[N][27],g[N][27],h[N][27],dp[N][N][N];
const int MOD = 1e8;

inline int M(const int &x){
	return (x%MOD+MOD)%MOD;
}

inline void build(){
	for(int i=n;i;i--){
		for(int j=1;j<=26;j++){
			f[i-1][j]=f[i][j];
			g[i-1][j]=g[i][j];
			h[i-1][j]=h[i][j];
		}
		f[i-1][a[i]-'a'+1]=i;
		g[i-1][b[i]-'a'+1]=i;
		h[i-1][c[i]-'a'+1]=i;
	}
}

int F(int i,int j,int k){
	if(dp[i][j][k]) return dp[i][j][k];
	for(int s=1;s<=26;s++){
		if(f[i][s] && g[j][s] && h[k][s]) dp[i][j][k]=M(dp[i][j][k]+F(f[i][s],g[j][s],h[k][s]));
	}
	if(i || j || k) dp[i][j][k]++;
	return dp[i][j][k]=M(dp[i][j][k]);
}

signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>a>>b>>c;
	a=" "+a;b=" "+b;c=" "+c;
	build();
	cout<<F(0,0,0)<<'\n';
	return 0;
}

P3856:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 105;
int ytxy,zyb,zbzQ;
string a,b,c;
int f[N][27],g[N][27],h[N][27],dp[N][N][N];

inline int M(const int &x){
	return x;
}

inline void build(){
	for(int i=ytxy;i;i--){
		for(int j=1;j<=26;j++){
			f[i-1][j]=f[i][j];
		}
		f[i-1][a[i]-'a'+1]=i;
	}
	for(int i=zyb;i;i--){
		for(int j=1;j<=26;j++){
			g[i-1][j]=g[i][j];
		}
		g[i-1][b[i]-'a'+1]=i;
	}
	for(int i=zbzQ;i;i--){
		for(int j=1;j<=26;j++){
			h[i-1][j]=h[i][j];
		}
		h[i-1][c[i]-'a'+1]=i;
	}
}

int F(int i,int j,int k){
	if(dp[i][j][k]) return dp[i][j][k];
	for(int s=1;s<=26;s++){
		if(f[i][s] && g[j][s] && h[k][s]) dp[i][j][k]=M(dp[i][j][k]+F(f[i][s],g[j][s],h[k][s]));
	}
	if(i || j || k) dp[i][j][k]++;
	return dp[i][j][k]=M(dp[i][j][k]);
}

signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>a>>b>>c;
	ytxy=a.size();zyb=b.size();zbzQ=c.size();
	a=" "+a;b=" "+b;c=" "+c;
	build();
	cout<<F(0,0,0)<<'\n';
	return 0;
}

标签:P3856,27,const,int,序列,build,公共,TJOI2008
From: https://www.cnblogs.com/zheyuanxie/p/p1819-p3856.html

相关文章

  • 最近公共祖先 RMQ
    就是把LCA问题转化为RMQ问题转化之前先要了解欧拉序列:对一棵树进行DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为2n-1的序列,这个序列被称作这棵树的欧拉序列。比如下面这个树:(2连3和4)1->2->3        ->4->5其序列就是123......
  • 最近公共祖先 算法总结
    现知LCA算法有倍增、Tarjan、树链剖分再LCA问题上各自的特点如下表倍增Tarjan树链剖分数组fa[u][i],depfa,vis,query,ansfa,dep,size_tree,heavy_child,top_chain思路深搜打表,跳跃查询深搜回溯,离线查询二重深搜打表,跳跃查询时间复杂度O((n+m)lo......
  • 最近公共祖先 树链剖分
    例题:洛谷P3379【模板】最近公共祖先(LCA)https://www.luogu.com.cn/problem/P3379首先是几个概念重儿子:父结点所有子树中最大的子树的根节点(只有一个或没有)轻儿子:父结点除了重儿子以外的所有子结点(没有或有很多个)重边:父结点和重儿子连的边轻边:父结点和轻儿子连的边重链:相接......
  • 最近公共祖先 Tarjan算法
    例题:洛谷P3379【模板】最近公共祖先(LCA)https://www.luogu.com.cn/problem/P3379tarjan算法是利用了并查集来求LCA的,时间复杂度比倍增低,是O(n+m)#include<iostream>#include<vector>#include<utility>#defineforup(i,l,r)for(inti=l;i<=r;i++)#definefordown(i,l,r)fo......
  • 最近公共祖先
    倍增求LCA①初始化:通过bfs初始化两个数组depth[],fa[]\(\quad\)\(\quad\)depth[n]:表示深度(到根节点的距离加1)\(\quad\)\(\quad\)fa[i][j]:表示从i开始,向上走\(2^j\)步所能到的节点编号(\(0\leqslantj\leqslantlog_2n\))\(\quad\)\(\quad\)\(......
  • 「学习笔记」tarjan求最近公共祖先
    Tarjan算法是一种离线算法,需要使用并查集记录某个结点的祖先结点。并没有传说中的那么快。过程将询问都记录下来,将它们建成正向边和反向边。在dfs的过程中,给走过的节点打上标记,同时维护并查集,这里利用了回溯的思想,如果\(u\)节点的这棵子树没搜完,那么fa[u]=u;,搜完后......
  • 两个链表的第一个公共结点
    使用空间存储节点的解法classSolution{public:set<ListNode*>s;ListNode*findFirstCommonNode(ListNode*headA,ListNode*headB){for(autoi=headA;i;i=i->next)s.insert(i);for(autoi=headB;i;i=i->next)......
  • 【LeetCode动态规划#14】子序列系列题(最长递增子序列、最长连续递增序列、最长重复子
    最长递增子序列力扣题目链接(opensnewwindow)给你一个整数数组nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]是数组[0,3,1,6,2,2,7]的子序列。示例1:输入:nums=[10,9,2,5,3,7......
  • JS逆向实战13——某市公共资源交易中心Cookie混淆加密
    "本文地址:https://www.cnblogs.com/zichliang/p/17346860.html目标网站aHR0cDovL2xkZ2d6eS5obmxvdWRpLmdvdi5jbi9sZGp5engvanl4eC9saXN0LnNodG1s网站分析经过浏览器抓包,我们可知这个网站会先发出一个412请求,然后附带一个js然后返回正常的页面。经过我们代码测试可知如......
  • LCA(最近公共祖先)学习笔记
    前言没想到干完lca的时间比tarjan的还要长(我不能这么弱下去了!!)前置知识dfs序这东西有点类似于时间戳(dfn),但是它分为两部分(回溯之前和回溯之后)。并且dfs序还分为两种。这里只介绍一倍的dfs序。如上图,蓝色代表左端点,红色代表右端点,(学过Tarjan的都知道),蓝色其实就是这棵树的dfn(......