
本文共 3996 字,大约阅读时间需要 13 分钟。
题目
题目描述
给两个小写字母串 A , B A,B A,B ,请你计算这四类字符串的最小长度:- 非 B B B 子串的 A A A 的子串
- 非 B B B 子序列的 A A A 的子串
- 非 B B B 子串的 A A A 的子序列
- 非 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,r−1)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
发表评论
最新留言
关于作者
