BZOJ 4032: [HEOI2015]最短不公共子串 【DP+后缀自动机】

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/C20181220_xiang_m_y/article/details/88942760

题面:

Description
在虐各种最长公共子串、子序列的题虐的不耐烦了之后,你决定反其道而行之。
下面,给两个小写字母串A,B,请你计算:
(1) A的一个最短的子串,它不是B的子串
(2) A的一个最短的子串,它不是B的子序列
(3) A的一个最短的子序列,它不是B的子串
(4) A的一个最短的子序列,它不是B的子序列
A和B的长度都不超过2000

题目分析:

这题本质上就是个DP,只不过对子序列的DP可以直接用数组,对子串的DP需要借助后缀自动机。

  • 第一问:
    对B串建SAM,暴力枚举A的每个子串,在SAM上走,若失配则计入答案。

  • 第二问:
    设g[i][j]表示B串的第i个字符之后最早出现的字符j的位置,暴力枚举A的每个子串,按照g贪心地走,若失配则计入答案。

  • 第三问:
    对B串建SAM,设f[i][j]表示考虑了A的前i个字符,当前在SAM上的状态为j的最小匹配长度,根据A[i+1]来转移,若j无A[i+1]的后继则计入答案。

  • 第四问:
    设f[i][j]表示考虑了A的前i个字符,当前在B串序列上匹配到第j位的最小匹配长度,根据A[i+1]来转移,若g[j][A[i+1]]为空则计入答案。

以上内容参考自Claris’ Blog

总结一下会发现,子序列问题就是考虑前i位,子串问题就暴力枚举起点,在B串上匹配就记一下匹配到哪个状态,总之这是个很好的DP题(虽然做完之后感觉很套路? )。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 4005
#define maxc 26
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m,ans1=inf,ans2=inf,ans3=inf,ans4=inf;
char a[maxn],b[maxn];
inline void chkmin(int &a,int b){if(a>b) a=b;}
namespace String{
	int fail[maxn]={-1},ch[maxn][maxc],len[maxn],sz,last;
	void sa_extend(int c){
		int p=last,cur=++sz,q;
		len[last=cur]=len[p]+1;
		for(;p!=-1&&!ch[p][c];p=fail[p]) ch[p][c]=cur;
		if(p==-1) fail[cur]=0;
		else if(len[p]+1==len[q=ch[p][c]]) fail[cur]=q;
		else{
			int clone=++sz; len[clone]=len[p]+1,fail[clone]=fail[q];
			fail[q]=fail[cur]=clone;
			memcpy(ch[clone],ch[q],sizeof ch[q]);
			for(;p!=-1&&ch[p][c]==q;p=fail[p]) ch[p][c]=clone;
		}
	}
	int f[2][maxn],now;
	void solve(){
		for(int i=1;i<=m;i++) sa_extend(b[i]);
		for(int i=1;i<=n;i++) for(int j=i,p=0;j<=n;j++) if(!(p=ch[p][a[j]])) {chkmin(ans1,j-i+1);break;}
		memset(f[now],0x3f,sizeof f[now]),f[now][0]=0;
		for(int i=1;i<=n;i++,now=!now){
			memset(f[!now],0x3f,sizeof f[!now]);
			for(int j=0;j<=sz;j++) if(f[now][j]!=inf){
				chkmin(f[!now][j],f[now][j]);
				if(ch[j][a[i]]) chkmin(f[!now][ch[j][a[i]]],f[now][j]+1);
				else chkmin(ans3,f[now][j]+1);
			}
		}
	}
}
namespace Sequence{
	int g[maxn][maxc],f[2][maxn],now;
	void solve(){
		for(int j=0;j<maxc;j++) for(int i=m-1;i>=0;i--) g[i][j]=b[i+1]==j?i+1:g[i+1][j];
		for(int i=1;i<=n;i++) for(int j=i,k=0;j<=n;j++) if(!(k=g[k][a[j]])) {chkmin(ans2,j-i+1);break;}
		memset(f[now],0x3f,sizeof f[now]),f[now][0]=0;
		for(int i=1;i<=n;i++,now=!now){
			memset(f[!now],0x3f,sizeof f[!now]);
			for(int j=0;j<=m;j++) if(f[now][j]!=inf){
				chkmin(f[!now][j],f[now][j]);
				if(g[j][a[i]]) chkmin(f[!now][g[j][a[i]]],f[now][j]+1);
				else chkmin(ans4,f[now][j]+1);
			}
		}
	}
}
int main()
{
	scanf("%s%s",a+1,b+1),n=strlen(a+1),m=strlen(b+1);
	for(int i=1;i<=n;i++) a[i]-='a';
	for(int i=1;i<=m;i++) b[i]-='a';
	String::solve();
	Sequence::solve();
	printf("%d\n",ans1==inf?-1:ans1);
	printf("%d\n",ans2==inf?-1:ans2);
	printf("%d\n",ans3==inf?-1:ans3);
	printf("%d\n",ans4==inf?-1:ans4);
}

猜你喜欢

转载自blog.csdn.net/C20181220_xiang_m_y/article/details/88942760
今日推荐