day1
有长度2000的字符串$A$和$B$;
求最短$A$的子串$AA$,使得$AA$不是$B$的子串;
求最短$A$的子串$AA$,使得$AA$不是$B$的子序列;
求最短$A$的子序列$AA$,使得$AA$不是$B$的子串;
求最短$A$的子序列$AA$,使得$AA$不是$B$的子序列;
求最短$A$的子串$AA$,使得$AA$不是$B$的子串
hash + 二分
首先对$A$和$B$串进行$hash$,然后二分答案$ans$,对长度为$ans$的子串$AA$进行枚举,然后到$B$去查询,复杂度$O(n^2logn)$。
由于$hash$用得熟透了,这里就不写了。
后缀数组
对字符串$S = AB$进行后缀排序,中间插入一个$dollor$符号。
那么,要满足$AA$既是$A$的子串,又不是$B$的子串,不妨对所有$A$的后缀$suf_{i}$进行判断:
- 排名比$suf_{i}$小,且最接近$suf_{i}$的$B$的后缀$suf_{j}$
- 排名比$suf_{i}$大,且最接近$suf_{i}$的$B$的后缀$suf_{k}$
容易知道,对于$suf_{i}$而言,对答案的贡献是$max(lcp(suf_i,suf_j) , lcp(suf_i,suf_k))+1$。且要特判这个长度完全在$A$范围内。
在这个时候,$AA$跟$B$的任何后缀的$LCP$都不会是$AA$。
时间复杂度$O(n^2)$。
1 | const int inf = 1e9+7; |
后缀自动机+bfs
不妨对$A$串和$B$串分别构建一个$SAM$,然后再进行$bfs$,状态为{posa,posb,step},表示A串走到的状态,B串走到的状态以及当前步数(也就是最短长度)。我们知道$SAM$走出的任一路径都是一个子串,如果A串能够通过字符c走到下一个状态,而此时B串无法通过字符c走到下一个状态,说明找到答案。
由于自动机的$SIZE$是$O(n)$的,所以搜索时的状态数不会超过$O(n^2)$。
1 | struct SAM{ |
求最短$A$的子串$AA$,使得$AA$不是$B$的子序列
序列自动机 + 二分
首先构造$B$串的序列自动机,然后二分答案$ans$,再通过暴力判断。二分答案是$O(logn)$的,枚举A串的长度为$ans$的子串是$O(n)$的,暴力判断是$O(n)$的,所以复杂度是$O(n^2logn)$的。
1 | int solve2(){ |
序列自动机
通过上文继续思考,可以发现,其实我们只需要枚举$A$串的起点,然后不断地开始添加连续的字符,假设$ j<k $而$a[i..j]$不满足,那么a[i..k]肯定也不满足,所以到达j时可以剪枝(虽然理论复杂度不变)。
那么就可以直接省去一个二分的复杂度了。
- 做此类题目不妨先想一想容易想的做法,然后试着去优化复杂度。
后缀自动机 + 序列自动机 + bfs
通过这里我们发现,序列自动机可以通用的处理“子序列”的问题。
首先构造A的SAM和B的序列自动机,然后如上的bfs便可。
显然复杂度也是$O(n^2)$的。
1 | struct SEM{ |
求最短$A$的子序列$AA$,使得$AA$不是$B$的子串
除了后缀自动机 + 序列自动机 + bfs的做法,还可以DP。
令$dp[i]$表示状态$i$能够接受的最短子序列长度。
如果对于字符$c$,$son[i][c]$存在,则$dp[son[i][c]]=min(dp[i]+1,dp[son[i][c]])$,
否则$ ans=min(ans , dp[i]+1) $
1 | int solve3(){ |
求最短$A$的子序列$AA$,使得$AA$不是$B$的子序列
除了序列自动机 + 序列自动机 + bfs的做法,还可以DP,同上。
1 | struct node{ |
完整代码
1 |
|