[BZOJ4032][HEOI2015]最短不公共子串(后缀自动机+序列自动机+DP)

[BZOJ4032][HEOI2015]最短不公共子串(后缀自动机+序列自动机+DP)

题面

给两个小写字母串A,B,请你计算:
(1) A的一个最短的子串,它不是B的子串
(2) A的一个最短的子串,它不是B的子序列
(3) A的一个最短的子序列,它不是B的子串
(4) A的一个最短的子序列,它不是B的子序列

分析

先考虑(1)(3),对B建SAM

对于询问(1).直接枚举子串左端点。对于每个左端点,向右扫的同时在SAM上匹配,当第一次失配的时候更新答案并break。

对于询问(3).设\(f_{i,j}\)为SAM节点\(j\)在A的前\(i\)位中匹配到的最长子序列长度。
那么显然有\(f_{i,\delta(j,A_i)}=\max(f_{i-1,j}+1)(\delta(j,A_i) \neq \text{NULL})\),其中\(\delta\)为自动机的转移函数。
最终答案为\(f_{n,q_0}\),其中\(q_0\)为SAM的起始状态,代表空串


考虑(2)(4)如何做。如果我们能构造出一个自动机,它能把询问串在某个串的所有子序列中匹配,那么就可以套用(1)(3)的方法。那么它的转移函数是根据子序列的,即\(\delta(x,c)\)表示\(x\)代表串后加一个字符\(c\)能匹配到的子序列。

那么就可以用到序列自动机。其实很多人都在不知不觉中用过这个方法。其实很简单,\(\delta(i,c)\)为:匹配到以\(i\)结尾的子序列的串,后加一个字符c能够匹配到的子序列位置。我们直接\(\delta(i,c)\)为位置\(i\)之后第一次出现字符\(c\)的位置即可。因为贪心来讲,跳到最先出现的位置会更优,相当于给后面更多可能选项。
如我们要在\(\texttt{aabacba}\)找到一个子序列与\(\text{abc}\)匹配,那么跳的位置是1,3,5.
构建算法很简单,直接从后往前扫描一遍,维护每个字符上一次出现的位置即可。注意实现上为了区分根节点和空状态,我们要把点的编号增加一位,如状态3对应的是结尾在第二位的子序列,1表示空串,0表示NULL。

struct seqt {
	//注意序列自动机的编号为实际位置+1
	//1代表空串,2代表第1位,3代表第2位... 
	int ch[maxn+5][maxc+5];
	int last[maxc+5];//存储每个字符上一次出现的位置 
	const int root=1;
	inline int trans(int x,char c) {
		return ch[x][c-'a'];
	}
	void build(char *s) {//无log做法 
		int len=strlen(s+1);
		for(int i=len;i>=1;i--){
			//现在更新位置i对应的节点i+1 
			for(int j=0;j<maxc;j++) ch[i+1][j]=last[j];
			last[s[i]-'a']=i+1;
		}
		for(int j=0;j<maxc;j++) ch[1][j]=last[j];

	}

} S;

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define INF 0x3f3f3f3f
#define maxn 2000
#define maxc 26
using namespace std;
struct SAM {
#define len(x) (t[x].len)
#define link(x) (t[x].link)
	struct node {
		int ch[maxc];
		int link;
		int len;
	} t[maxn*2+5];
	const int root=1;
	int last=root;
	int ptr=1;
	void extend(int c) {
		int p=last,cur=++ptr;
		len(cur)=len(p)+1;
		while(t[p].ch[c]==0) {
			t[p].ch[c]=cur;
			p=link(p);
		}
		if(p==0) link(cur)=root;
		else {
			int q=t[p].ch[c];
			if(len(p)+1==len(q)) link(cur)=q;
			else {
				int clo=++ptr;
				link(clo)=link(q);
				for(int i=0; i<maxc; i++) t[clo].ch[i]=t[q].ch[i];
				len(clo)=len(p)+1;
				link(q)=link(cur)=clo;
				while(t[p].ch[c]==q) {
					t[p].ch[c]=clo;
					p=link(p);
				}
			}
		}
		last=cur;
	}
	void build(char *s) {
		int len=strlen(s+1);
		for(int i=1; i<=len; i++) extend(s[i]-'a');
	}
	inline int trans(int x,char c) {
		return t[x].ch[c-'a'];
	}
} T;
struct seqt {
	//注意序列自动机的编号为实际位置+1
	//1代表空串,2代表第1位,3代表第2位... 
	int ch[maxn+5][maxc+5];
	int last[maxc+5];//存储每个字符上一次出现的位置 
	const int root=1;
	inline int trans(int x,char c) {
		return ch[x][c-'a'];
	}
	void build(char *s) {//无log做法 
		int len=strlen(s+1);
		for(int i=len;i>=1;i--){
			//现在更新位置i对应的节点i+1 
			for(int j=0;j<maxc;j++) ch[i+1][j]=last[j];
			last[s[i]-'a']=i+1;
		}
		for(int j=0;j<maxc;j++) ch[1][j]=last[j];

	}

} S;

int n,m;
char a[maxn+5],b[maxn+5];
int solve1() {
	int ans=INF;
	for(int i=1; i<=n; i++) {
		int x=T.root;
		for(int j=i; j<=n; j++) {
			x=T.trans(x,a[j]);
			if(x==0) {
				ans=min(ans,j-i+1);
				break;
			}
		}
	}
	if(ans==INF) ans=-1;
	return ans;
}
int solve2() {
	int ans=INF;
	for(int i=1; i<=n; i++) {
		int x=S.root;
		for(int j=i; j<=n; j++) {
			x=S.trans(x,a[j]);
			if(x==0) {
				ans=min(ans,j-i+1);
				break;
			}
		}
	}
	if(ans==INF) ans=-1;
	return ans;
}
int solve3() {
	static int f[2][maxn*2+5];
	//f[i][j]表示A串的前i位的子串中,能走到自动机上j节点的最短子串
	//那么答案就是f[i][0](到空节点就是不匹配)
	//第一维可以滚动数组
	memset(f,0x3f,sizeof(f));
	int now=0;
	f[now][T.root]=0;
	for(int i=1; i<=n; i++) {
		now^=1;
		f[now][T.root]=0;
		for(int j=1; j<=T.ptr; j++) f[now][j]=f[now^1][j];
		for(int j=1; j<=T.ptr; j++) {
			int to=T.trans(j,a[i]);
			f[now][to]=min(f[now][to],f[now^1][j]+1);
		}
	}
	if(f[now][0]==INF) return -1;
	else return f[now][0];
}
int solve4() {
	static int f[2][maxn*2+5];
	memset(f,0x3f,sizeof(f));
	int now=0;
	f[now][1]=0;
	for(int i=1; i<=n; i++) {
		now^=1;
		f[now][1]=0;
		for(int j=1; j<=m+1; j++) f[now][j]=f[now^1][j];
		for(int j=1; j<=m+1; j++) {
			int to=S.trans(j,a[i]);
			f[now][to]=min(f[now][to],f[now^1][j]+1);
		}
	}
	if(f[now][0]==INF) return -1;
	else return f[now][0];
}
int main() {
	scanf("%s",a+1);
	scanf("%s",b+1);
	n=strlen(a+1);
	m=strlen(b+1);
	T.build(b);
	S.build(b);
	printf("%d\n",solve1());
	printf("%d\n",solve2());
	printf("%d\n",solve3());
	printf("%d\n",solve4());
}
/*
hack:
abaaa
aabaa
*/
posted @ 2020-04-20 21:28  birchtree  阅读(161)  评论(0编辑  收藏  举报