BZOJ4032 [HEOI2015]最短不公共子串 【后缀自动机 + 序列自动机 + dp】
题目链接
题解
首先膜\(hb\)
空手切神题
一问\(hash\),二问枚举
三问\(trie\)树,四问\(dp\)
南二巨佬神\(hb\)
空手吊打自动机
\(orz orz orz orz orz orz orz\)
咳。说正解
要处理子串,直接搬上后缀自动机
要处理子序列,直接搬上序列自动机【雾】
何为序列自动机##
序列自动机其实很简单,就是具有识别所有子序列功能的自动机
建机原理及过程及其简单,只需要从后往前扫一遍,对每一个位置建立一个节点,该节点的各个边连向最晚出现的对应的字符对应的节点
只需记录下每个字符最晚出现的节点就可以\(O(n|s|)\)建机了 【\(|s|\)为字符集大小】
代码实现:
struct LAM{
int ch[maxn][26],last[26],cnt;
void ins(int x){
int p = ++cnt;
for (int i = 0; i < 26; i++)
ch[p][i] = last[i];
last[x] = p;
}
void init(){
for (int i = 0; i < 26; i++)
ch[1][i] = last[i];
}
}lam;
......
lam.cnt = 1;
for (int i = len; i; i--) lam.ins(s[i] - 'a');
lam.init();
第一、二问##
只需枚举\(A\)子串的起点,然后看看能在自动机中走多远即可
第三、四问##
与\(A\)的子序列有关,就上\(dp\)
设\(f[i][j]\)表示\(A\)的前\(i\)个字符形成的所有子序列匹配到自动机\(j\)号节点的最短长度
如果\(i\)不选
\[f[i][j] = f[i - 1][j]
\]
如果\(i\)被选,那么枚举是从哪一个节点转移来的
设上一个节点为\(u\)
\[f[i][j] = min\{f[i - 1][u] + 1\}
\]
【注意这里是枚举\(u\)】
答案即为\(f[len][0]\) 【\(0\)表示空,即不匹配】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 4005,maxm = 100005,INF = 0x3f3f3f3f;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
struct SAM{
int ch[maxn][26],pre[maxn],step[maxn],last,cnt;
void ins(int x){
int p = last,np = ++cnt; step[np] = step[p] + 1; last = np;
while (p && !ch[p][x]) ch[p][x] = np,p = pre[p];
if (!p) pre[np] = 1;
else {
int q = ch[p][x];
if (step[q] == step[p] + 1) pre[np] = q;
else {
int nq = ++cnt; step[nq] = step[p] + 1;
for (int i = 0; i < 26; i++) ch[nq][i] = ch[q][i];
pre[nq] = pre[q]; pre[np] = pre[q] = nq;
while (ch[p][x] == q) ch[p][x] = nq,p = pre[p];
}
}
}
}sam;
struct LAM{
int ch[maxn][26],last[26],cnt;
void ins(int x){
int p = ++cnt;
for (int i = 0; i < 26; i++)
ch[p][i] = last[i];
last[x] = p;
}
void init(){
for (int i = 0; i < 26; i++)
ch[1][i] = last[i];
}
}lam;
char A[maxn],B[maxn];
int lena,lenb;
void solve1(){
int ans = INF;
for (int i = 1,j; i <= lena; i++){
int u = 1,cnt = 0;
for (j = i; j <= lena; j++){
++cnt;
if (!sam.ch[u][A[j] - 'a']) break;
u = sam.ch[u][A[j] - 'a'];
}
if (j <= lena) ans = min(ans,cnt);
}
if (ans == INF) puts("-1");
else printf("%d\n",ans);
}
void solve2(){
int ans = INF;
for (int i = 1,j; i <= lena; i++){
int u = 1,cnt = 0;
for (j = i; j <= lena; j++){
++cnt;
if (!lam.ch[u][A[j] - 'a']) break;
u = lam.ch[u][A[j] - 'a'];
}
if (j <= lena) ans = min(ans,cnt);
}
if (ans == INF) puts("-1");
else printf("%d\n",ans);
}
int f[2005][maxn];
void solve3(){
memset(f,INF,sizeof(f));
f[0][1] = 0;
for (int i = 1; i <= lena; i++){
for (int j = 1; j <= sam.cnt; j++) f[i][j] = min(f[i][j],f[i - 1][j]);
for (int j = 1; j <= sam.cnt; j++){
int to = sam.ch[j][A[i] - 'a'];
f[i][to] = min(f[i][to],min(f[i - 1][to],f[i - 1][j] + 1));
}
}
if (f[lena][0] == INF) puts("-1");
else printf("%d\n",f[lena][0]);
}
void solve4(){
memset(f,INF,sizeof(f));
f[0][1] = 0;
for (int i = 1; i <= lena; i++){
for (int j = 1; j <= lam.cnt; j++) f[i][j] = min(f[i][j],f[i - 1][j]);
for (int j = 1; j <= lam.cnt; j++){
int to = lam.ch[j][A[i] - 'a'];
f[i][to] = min(f[i][to],min(f[i - 1][to],f[i - 1][j] + 1));
}
}
if (f[lena][0] == INF) puts("-1");
else printf("%d\n",f[lena][0]);
}
int main(){
scanf("%s",A + 1); lena = strlen(A + 1);
scanf("%s",B + 1); lenb = strlen(B + 1);
sam.last = sam.cnt = 1; lam.cnt = 1;
for (int i = 1; i <= lenb; i++) sam.ins(B[i] - 'a');
for (int i = lenb; i; i--) lam.ins(B[i] - 'a');
lam.init();
solve1();
solve2();
solve3();
solve4();
return 0;
}