[BZOJ4032] [HEOI2015] 最短不公共子串 - 后缀自动机,序列自动机,BFS

4032: [HEOI2015]最短不公共子串

Time Limit: 10 Sec   Memory Limit: 256 MB
Submit: 398   Solved: 189
[ Submit][ Status][ Discuss]

Description

 在虐各种最长公共子串、子序列的题虐的不耐烦了之后,你决定反其道而行之。

一个串的“子串”指的是它的连续的一段,例如bcd是abcdef的子串,但bde不是。
一个串的“子序列”指的是它的可以不连续的一段,例如bde是abcdef的子串,但bdd不是。
下面,给两个小写字母串A,B,请你计算:
(1) A的一个最短的子串,它不是B的子串
(2) A的一个最短的子串,它不是B的子序列
(3) A的一个最短的子序列,它不是B的子串
(4) A的一个最短的子序列,它不是B的子序列

Input

有两行,每行一个小写字母组成的字符串,分别代表A和B。

Output

输出4行,每行一个整数,表示以上4个问题的答案的长度。如果没有符合要求的答案,输出-1.

Sample Input

aabbcc
abcabc

Sample Output

2
4
2
4

HINT

 对于100%的数据,A和B的长度都不超过2000


Source

[ Submit][ Status][ Discuss]

HOME   Back

其实序列自动机就是一个纸张的能遍历出所有子序列的自动机。。其实就是每个点的右边的不同字符的最近出现位置。

那么我们这些询问怎么做呢,首先我们建出每个串的后缀自动机和序列自动机,这样我们就能遍历所有的子序列了。

然后这样第一个询问随便用两个后缀自动机来BFS一下就好了(最简单的询问,trie或者hash都可以)。

那么第二个询问怎么搞呢,这个时候我们就发现一个显然对于4个询问都承认的限制:若用dp[i][j]表示第一个串在自动机上匹配到i位置,第二个串在自动机上匹配到j位置,那么显然dp[i][j]最小。

所以我们只要BFS就可以满足这样的要求。我们对于第二个询问匹配第一个串的后缀自动机(遍历所有子串)和序列自动机(遍历所有子序列),同理我们匹配不同的自动机,可以解决这个问题。

时间复杂度O(n^2*26)

#include"bits/stdc++.h"
#define F(i,l,r) for(int i=l;i<=r;i++)
#define D(i,r,l) for(int i=r;i>=l;i--)
using namespace std;
const int N=2005,M=26;
char s1[N],s2[N];
struct SequenceAutoMaton{
	int a[N],e[N][M],n,last[M];
	void pre(char*s){
		n=strlen(s+1);
		D(i,n,1){
			memcpy(e[i+1],last,sizeof(int[M]));
			a[i]=s[i]-'a';last[a[i]]=i+1;
		}
		memcpy(e[1],last,sizeof(int[M]));
	}
} sqa[2];
struct SuffixAutoMaton{
	int a[N*2],e[N*2][M],fail[N*2],d[N*2],last,n,nd;
	SuffixAutoMaton(){nd=last=1;}
	void extend(int c){
		int np=++nd,p=last;d[np]=d[last]+1;
		for(;!e[p][c]&&p;e[p][c]=np,p=fail[p]);
		if(!p)fail[np]=1;
		else {
			int q=e[p][c],nq;
			if(d[q]==d[p]+1)fail[np]=q;
			else {
				nq=++nd;d[nq]=d[p]+1;
				memcpy(e[nq],e[q],sizeof(int[M]));
				fail[nq]=fail[q];fail[np]=fail[q]=nq;
				for(;e[p][c]==q;e[p][c]=nq,p=fail[p]);
			}
		}
		last=np;
	}
	void pre(char*s){
		n=strlen(s+1);
		F(i,1,n){
			a[i]=s[i]-'a';
			extend(a[i]);
		}
	}
} sam[2];
int dp[2*N][2*N],q1[N*N],q2[N*N],l,r;
template<class T1,class T2>
void DP(T1&x,T2&y){
	q1[1]=q2[1]=1;
	memset(dp,0,sizeof(dp));
	for(l=r=1;l<=r;l++){
		F(i,0,25){
			int dx=x.e[q1[l]][i],dy=y.e[q2[l]][i];
			if(!dx)continue;
			if(!dy){
				printf("%d\n",dp[q1[l]][q2[l]]+1);
				return;
			}
			if(dp[dx][dy])continue;
			dp[dx][dy]=dp[q1[l]][q2[l]]+1;
			++r;q1[r]=dx;q2[r]=dy;
		}
	}
	puts("-1");
}
int main(){
	scanf("%s\n%s",s1+1,s2+1);
	sqa[0].pre(s1),sqa[1].pre(s2);
	sam[0].pre(s1),sam[1].pre(s2);
	DP(sam[0],sam[1]);DP(sam[0],sqa[1]);
	DP(sqa[0],sam[1]);DP(sqa[0],sqa[1]); 
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值