题目描述
LuoguP4112
给两个小写字母串A,B,请你计算:
(1) A的一个最短的子串,它不是B的子串
(2) A的一个最短的子串,它不是B的子序列
(3) A的一个最短的子序列,它不是B的子串
(4) A的一个最短的子序列,它不是B的子序列
字符串长度不超过2000。
思路
数据结构,思想
- 后缀自动机(用来表示出所有字串)
- 序列自动机(用来表示出所有子序列)
- 记忆化搜索
略提一嘴序列自动机
- 这是我见过的最简单的自动机了(如果你硬要说kmp也是自动机,我也没有办法),这个自动机能够把一个字符串的所有子序列表示出来,时空俱为 O ( ∣ S ∣ ∣ ∑ ∣ ) O(|S||\sum|) O(∣S∣∣∑∣), ∣ S ∣ |S| ∣S∣表示字符串长度, ∣ ∑ ∣ |\sum| ∣∑∣表示字符集大小
- 可以认为自动机中 i + 1 i+1 i+1号节点对应于原串中的第 i i i个位置。我们让第 i + 1 i+1 i+1个节点的字符 c c c的转移,连向原串中在他后面的最近的字符 c c c在自动机中对应的节点。
- 比如字符串abac,自动机中第二个节点的’a’的转移,就会连到第四个节点(对应于原串中的第三个位置)。而一号节点’a’转移连向二号,'b’转移连向三号,'c’转移连向五号。
- 我们发现,如果有这样一个子序列,对应于原串中的位置是1,2,5,7,8,而且第五个字符与第四个字符相等,那么我们选择1,2,4,7,8是完全等价的。因此,如果我们把一个字符的转移只连到他后面离他最近的那个位置上,也是没有问题的,连到后面能表示的子序列,他都能表示。
- 建立的话,可以从后往前扫一遍,也可以增量构建,个人觉得没有太大区别
- 在这道题中,
为了好写,我选择了增量构建。 - 我后缀三毒瘤都学过了,但是这个以前是真的没有听说过。
真·思路
- 先考虑把两个字符串的后缀自动机与序列自动机都建立出来。
- 然后每一个询问就都是看一下在一个自动机中,且不在另一个中的最短字符串是什么。
- 具体来说,我们可以考虑记忆化搜索第一个自动机跑到 i i i号结点,第二个自动机跑到 j j j号结点时,还需要跑的最短距离 f i , j f_{i,j} fi,j,直接从他在自动机中能转移到的结点转移过来即可。
- 即,如果 i i i节点有个字符 c c c的转移指向 i ′ i^\prime i′, j j j节点有个字符 c c c的转移指向 j ′ j^\prime j′,那么 f i , j = m i n ( f i ′ , j ′ + 1 ) f_{i,j}=min(f_{i^\prime,j^\prime}+1) fi,j=min(fi′,j′+1),如果 i i i节点有转移 j j j节点没有,就说明你发现了一个在一个中而不在另一个中的字符串。
代码
#include<bits/stdc++.h>
#define LL long long
#define MAXN 2000
#define INF 10000000
using namespace std;
template<typename T>void Read(T &cn)
{
char c;int sig = 1;
while(!isdigit(c = getchar()))if(c == '-')sig = -1;cn = c-48;
while(isdigit(c = getchar()))cn = cn*10+c-48;cn*=sig;
}
template<typename T>void Write(T cn)
{
if(!cn){putchar('0');return;}
if(cn<0){putchar('-');cn = 0-cn;}
int wei = 0;T cm = 0;int cx = cn%10;cn=cn/10;
while(cn)wei++,cm = cm*10+cn%10,cn=cn/10;
while(wei--)putchar(cm%10+48),cm=cm/10;
putchar(cx+48);
}
int zhan[MAXN+1][26];
struct SAM{
struct node{
int link,len,ch[26];
void qing() {link = len = 0; memset(ch,0,sizeof(ch)); }
};
node t[MAXN*2+1];
int tlen,last;
void build(int cn) {t[tlen = last = 1].qing(); if(cn == 2)for(int i = 0;i<=25;i++)zhan[++zhan[0][i]][i] = 1; }
void jia(int cn)
{
int cur = ++tlen;
t[cur].qing();
t[cur].len = t[last].len+1;
for(;p && !t[p].ch[cn];p = t[p].link)t[p].ch[cn] = cur;
if(!p)t[cur].link = 1;
else{
int q = t[p].ch[cn];
if(t[p].len == t[q].len-1)t[cur].link = q;
else{
int cln = ++tlen;
t[cln] = t[q];
t[cln].len = t[p].len+1;
t[q].link = t[cur].link = cln;
for(;p && t[p].ch[cn] == q;p = t[p].link)t[p].ch[cn] = cln;
}
}
last = cur;
}
void jia2(int cn)
{
t[++tlen].qing();
for(int i = 1;i<=zhan[0][cn];i++)t[zhan[i][cn]].ch[cn] = tlen;
zhan[0][cn] = 0;
for(int i = 0;i<=25;i++)zhan[++zhan[0][i]][i] = tlen;
}
}S1,S2,S3,S4;
char c[MAXN+1],d[MAXN+1];
int clen,dlen;
int f[MAXN*2+1][MAXN*2+1];
void getit(char c[],int &n)
{
while(!isalpha(c[1] = getchar())); n = 1;
while(isalpha(c[++n] = getchar())); n--;
}
int suan_z(SAM &T1,SAM &T2,int cn,int cm)
{
if(!cm)return 0;
if(f[cn][cm] != -1)return f[cn][cm];
f[cn][cm] = INF;
for(int i = 0;i<=25;i++)if(T1.t[cn].ch[i])f[cn][cm] = min(f[cn][cm],suan_z(T1,T2,T1.t[cn].ch[i],T2.t[cm].ch[i])+1);
return f[cn][cm];
}
int suan(SAM &T1,SAM &T2)
{
for(int i = 1;i<=T1.tlen;i++)for(int j = 1;j<=T2.tlen;j++)f[i][j] = -1;
int lin = suan_z(T1,T2,1,1);
return lin <= clen ? lin : -1;
}
int main()
{
getit(c,clen); getit(d,dlen);
for(int i = 0;i<=25;i++)zhan[0][i] = 0; S1.build(1); S2.build(2); for(int i = 1;i<=clen;i++)S1.jia(c[i] - 'a'), S2.jia2(c[i] - 'a');
for(int i = 0;i<=25;i++)zhan[0][i] = 0; S3.build(1); S4.build(2); for(int i = 1;i<=dlen;i++)S3.jia(d[i] - 'a'), S4.jia2(d[i] - 'a');
Write(suan(S1,S3)); puts("");
Write(suan(S1,S4)); puts("");
Write(suan(S2,S3)); puts("");
Write(suan(S2,S4)); puts("");
return 0;
}