【刷题】LuoguP4112 [HEOI2015]最短不公共子串

题目描述

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值