[HEOI2015]最短不公共子串
发布日期:2021-05-07 01:05:11 浏览次数:27 分类:精选文章

本文共 3996 字,大约阅读时间需要 13 分钟。

题目

题目描述

给两个小写字母串 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 
#include
#include
#include
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
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
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
上一篇:VS Code中快速创建自定义代码模板的方法
下一篇:Vue的is属性

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年03月28日 08时17分43秒