[HEOI2015]最短不公共子串

38 篇文章 0 订阅
13 篇文章 0 订阅

题目

传送门 to LOJ

题目描述
给两个小写字母串 A , B A,B A,B ,请你计算这四类字符串的最小长度:

  1. B B B 子串的 A A A 的子串
  2. B B B 子序列的 A A A 的子串
  3. B B B 子串的 A A A 的子序列
  4. B B B 子序列 A A A 的子序列

数据范围与提示
n = m a x ( ∣ A ∣ , ∣ B ∣ ) ≤ 2000 n=max(|A|,|B|)\le 2000 n=max(A,B)2000 。这里 n n n 只是为了后面写复杂度好看一点。

思路

P A R T    1 \tt PART \;1 PART1

一种做法是,二分答案,把 ∣ A ∣ |A| A 个该长度的子串取出来(利用哈希),然后在 B B B 里查是否存在。复杂度 O ( n log ⁡ 2 n ) \mathcal O(n\log^2n) O(nlog2n) 。(可行性源于,若长度为 L L L A A A 的子串已经不存在,那么 L + 1 L+1 L+1 长度必然也不存在。)

还有另一种做法,就是利用 S A M \tt SAM SAM 去掉不可行的子串。或许好打一点。

忽然想到第三种,最简单的暴力做法:把 A A A 的所有子串拿去做匹配……

P A R T    2 \tt PART\;2 PART2

只要选择的是 A A A 的子串都很好办——数量很有限。考虑用类似 d p \tt dp dp 的方式优化枚举就行。

f ( l , r ) f(l,r) f(l,r) 表示 A A A l l l r r r 的子串,要匹配到 B B B 的哪个前缀的子序列才行。在 B B B 尾端加入无穷多个通配符。那么有转移

f ( l , r ) = min ⁡ t > f ( l , r − 1 ) B t = A r t f(l,r)=\min_{t>f(l,r-1)}^{B_{t}=A_{r}}t f(l,r)=t>f(l,r1)minBt=Art

显然 f ( l , r ) f(l,r) f(l,r) l l l 这一维上单调不增。先枚举 r r r ,而后从小到大枚举 l l l ,就可实现 O ( 1 ) \mathcal O(1) O(1) 转移。

P A R T    3 \tt PART\;3 PART3

感觉更棘手了。因为序列我们维护不了。我们只会子串。咋整?将计就计,对 B B B 建立 S A M \tt SAM SAM 即可。

想一个暴力做法:把 A A A 的每个子序列拿去试着匹配 B B B 的子串。若是已经无法匹配了,那么最好就是以此处为结束点。所以,只有可匹配的才有价值。那么 S A M \tt SAM SAM 上每个点维护,匹配到此处的最短子序列。

转移的时候,对于一个 “可匹配” 的 y y y ,如果其 A x + 1 A_{x+1} Ax+1 儿子为空,那么 l e n y + 1 len_y+1 leny+1 为可行答案;否则打上可匹配 l e n y + 1 len_y+1 leny+1 标记。这里的 l e n len len 是指最短可匹配长度。

复杂度 O ( n 2 ) \mathcal O(n^2) O(n2) ,和 P A R T    2 \tt PART\;2 PART2 一样慢得很。

P A R T    4 \tt PART\;4 PART4

完了!我们根本不会处理子序列!我们只会 S A M \tt SAM SAM !天崩地裂,丘峦崩摧!

所以抛开自动机,直接 d p \tt dp dp 好了。用 f ( x , y ) f(x,y) f(x,y) 表示, A A A 的前 x x x 个字符中,有一个长度为 y y y 的子序列,使得 B B B 必须用前 f ( x , y ) f(x,y) f(x,y) 个字符才能完成匹配。(匹配的思想和 P A R T    3 \tt PART\;3 PART3 一致。)

转移就是,找到最小的 t t t 满足 t > f ( x , y ) t>f(x,y) t>f(x,y) B t = A x + 1 B_{t}=A_{x+1} Bt=Ax+1 那么有

f ( x + 1 , y + 1 ) = max ⁡ [ f ( x , y + 1 ) , t ] f(x+1,y+1)=\max[f(x,y+1),t] f(x+1,y+1)=max[f(x,y+1),t]

考虑到 f ( x , y ) f(x,y) f(x,y) 在两个维度上都递增,从小到大枚举 x x x 后,从大到小枚举 y y y 。这样 t t t 的取值范围就是越来越大的,就可以实现 O ( 1 ) \mathcal O(1) O(1) 转移了。

B B B 末尾加入无穷多通配符。答案就是让 f ( x , y ) f(x,y) f(x,y) 用到了通配符的 min ⁡ y \min y miny

总结

其实四个部分并不是非常难,但是杂糅在一起容易把人心态搞崩。如果我不用写博客的方式来推导,可能也会花费不少时间。

容易受到定式思维误导。其实 S A M \tt SAM SAM 是配角;真正起决定性作用的是 d p \tt dp dp 。我们引入 S A M \tt SAM SAM 只是为了字符串匹配而已。用自动机节点表示匹配情况是常见的 d p \tt dp dp 操作了;学过 A C \mathcal{AC} AC 自动机的童鞋肯定深有体会。

代码

我的 s o l v e 1 ( ) \sf solve1() solve1() 写复杂了。建议读者自行更改。

#include <cstdio> 
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 4005;
const int CharSiz = 26;

namespace SAM{
	int ch[MaxN][CharSiz];
	int fa[MaxN], len[MaxN];
	int lst = 1, cntNode = 1;
	void clear(){
		memset(ch,0,MaxN*CharSiz<<2);
		memset(fa,0,MaxN<<2);
		memset(len,0,MaxN<<2);
		lst = cntNode = 1;
	}
	void add(char c){
		int p = lst; // last one
		int now = lst = ++ cntNode;
		len[now] = len[p]+1; // pre
		for(; p && !ch[p][c]; p=fa[p])
			ch[p][c] = now; // new string
		if(!p) return void(fa[now] = 1);
		int x = ch[p][c]; // maybe parent
		if(len[x] == len[p]+1)
			return void(fa[now] = x);
		int y = ++ cntNode; // split x
		memcpy(ch[y],ch[x],CharSiz<<2);
		fa[y] = fa[x], fa[x] = y; // chain
		len[y] = len[p]+1, fa[now] = y;
		for(; ch[p][c]==x; p=fa[p])
			ch[p][c] = y; // change son
	}
	void build(char ppl[],int llt){
		clear(); // good habit
		for(int i=0; i<llt; ++i)
			add(ppl[i]);
	}
} using namespace SAM;

char A[MaxN], B[MaxN]; // [0,26)
int lenA, lenB; // [0,len)
int f[2][MaxN]; // whatever

void solve1(){
	build(A,lenA);
	memset(f[0],0,MaxN<<2);
	int x = 1, now = 0;
	for(int i=0; i<lenB; ++i){
		while(x && !ch[x][B[i]])
			now = len[x = fa[x]];
		x = ch[x][B[i]], ++ now;
		if(!x) x = 1, now = 0;
		for(int j=x; j; j=fa[j])
			f[0][j] = max(f[0][j],now);
	}
	int ans = MaxN; // infty
	for(int i=2; i<=cntNode; ++i){
		int x = len[fa[i]]+1;
		if(x < f[0][i]+1)
			x = f[0][i]+1;
		if(ans > x) ans = x;
	}
	if(ans != MaxN)
		printf("%d\n",ans);
	else puts("-1");
}

void solve2(){
	int ans = MaxN;
	memset(f,-1,2*MaxN<<2);
	for(int r=0; r<lenA; ++r){
		int fr = (r&1)^1;
		int t = lenB, p = t-1;
		for(int l=0; l<=r; ++l){
			for(; f[fr][l]<p; --p)
				if(B[p] == A[r])
					t = p; // update
			if((f[r&1][l] = t) == lenB)
				ans = min(ans,r-l+1);
//			printf("f[%d,%d] = %d\n",l,r,f[r&1][l]);
		}
	}
	if(ans != MaxN)
		printf("%d\n",ans);
	else puts("-1");
}

void solve3(){
	SAM::build(B,lenB);
	memset(f,0x3f,2*MaxN<<2);
	f[1][1] = 0; // initial
	int ans = MaxN;
	for(int i=0; i<lenA; ++i){
		int fr = (i&1)^1;
		for(int j=1,y; j<=cntNode; ++j)
			if(!(y = ch[j][A[i]]))
				ans = min(ans,f[fr][j]+1);
			else if(f[i&1][y] > f[fr][j]+1)
				f[i&1][y] = f[fr][j]+1;
		for(int j=1; j<=cntNode; ++j){
			f[i&1][j] = min(f[i&1][j],f[fr][j]);
			f[fr][j] = 0x3f3f3f3f;
		}
	}
	if(ans != MaxN)
		printf("%d\n",ans);
	else puts("-1");
}

void solve4(){
	memset(f,-1,2*MaxN<<2); // no cost
	for(int i=0; i<lenA; ++i){
		int fr = (i&1)^1;
		int t = lenB, p = t-1;
		for(int j=i+1; j; --j){
			for(; f[fr][j-1]<p; --p)
				if(B[p] == A[i])
					t = p; // update
			f[i&1][j] = max(f[fr][j],t);
		}
		f[i&1][0] = -1; // of course
	}
	for(int i=1; i<=lenA; ++i)
		if(f[(lenA-1)&1][i] == lenB){
			printf("%d\n",i); return ;
		}
	puts("-1");
}

int main(){
	scanf("%s %s",A,B);
	lenA = strlen(A);
	lenB = strlen(B);
	for(int i=0; i<lenA; ++i)
		A[i] -= 'a'; // hash
	for(int i=0; i<lenB; ++i)
		B[i] -= 'a'; // hash
	solve1(); solve2();
	solve3(); solve4(); 
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值