BZOJ4032:[HEOI2015]最短不公共子串(SAM)

Description

 在虐各种最长公共子串、子序列的题虐的不耐烦了之后,你决定反其道而行之。

一个串的“子串”指的是它的连续的一段,例如bcd是abcdef的子串,但bde不是。
一个串的“子序列”指的是它的可以不连续的一段,例如bde是abcdef的子串,但bdd不是。
下面,给两个小写字母串A,B,请你计算:
(1) A的一个最短的子串,它不是B的子串
(2) A的一个最短的子串,它不是B的子序列
(3) A的一个最短的子序列,它不是B的子串
(4) A的一个最短的子序列,它不是B的子序列

Input

有两行,每行一个小写字母组成的字符串,分别代表A和B。

Output

输出4行,每行一个整数,表示以上4个问题的答案的长度。如果没有符合要求的答案,输出-1.

Sample Input

aabbcc
abcabc

Sample Output

2
4
2
4

HINT

 对于100%的数据,A和B的长度都不超过2000

Solution

强行四合一?

(一)枚举$A$串的左端点,然后从左端点开始往后在$B$串的$SAM$上面跑,一旦失配就更新答案然后$break$

(二)预处理数组$next[i][j]$表示从$i$后面的第一次出现字母$j$的位置。预处理出$nextA$和$nextB$,然后枚举$A$左端点往后贪心,如果失配就更新答案然后$break$

(三)设$len[i]$表示在$B$串的$SAM$的$i$点的时候最短的长度。然后用$A$串在$B$的$SAM$上面跑。如果失配就更新答案,否则就更新$len$。

(四)设$len[i]$表示在$B$串的$i$位置的时候最短的长度。然后用$A$串的每一个字母,借$next$数组倒序去更新$len$。如果失配就更新答案,否则就更新$len$。至于为什么要倒序,其实是和背包差不多的原理,并不难想。

Code

  1 #include<iostream>
  2 #include<cstring>
  3 #include<cstdio>
  4 #define N (4009)
  5 using namespace std;
  6 
  7 char s[N],t[N];
  8 int slen,tlen,nextA[N][28],nextB[N][28],last[28],len[N];
  9 
 10 struct SAM
 11 {
 12     int son[N][28],fa[N],step[N],wt[N],od[N];
 13     int p,q,np,nq,last,cnt;
 14     SAM(){last=cnt=1;}
 15 
 16     void Insert(int x)
 17     {
 18         p=last; np=last=++cnt; step[np]=step[p]+1;
 19         while (p && !son[p][x]) son[p][x]=np, p=fa[p];
 20         if (!p) fa[np]=1;
 21         else
 22         {
 23             q=son[p][x];
 24             if (step[q]==step[p+1]) fa[np]=q;
 25             else
 26             {
 27                 nq=++cnt; step[nq]=step[p]+1;
 28                 memcpy(son[nq],son[q],sizeof(son[q]));
 29                 fa[nq]=fa[q]; fa[np]=fa[q]=nq;
 30                 while (son[p][x]==q) son[p][x]=nq, p=fa[p];
 31             }
 32         }
 33     }
 34 }SAM;
 35 
 36 void CalcNext()
 37 {
 38     memset(last,-1,sizeof(last));
 39     for (int i=slen; i>=0; --i)
 40     {
 41         for (int j=0; j<26; ++j) nextA[i][j]=last[j];
 42         last[s[i]-'a']=i;
 43     }
 44     memset(last,-1,sizeof(last));
 45     for (int i=tlen; i>=0; --i)
 46     {
 47         for (int j=0; j<26; ++j) nextB[i][j]=last[j];
 48         last[t[i]-'a']=i;
 49     }
 50 }
 51 
 52 void Sub1()
 53 {
 54     int ans=2e9;
 55     for (int i=1; i<=slen; ++i)
 56     {
 57         int now=1;
 58         for (int j=i; j<=slen; ++j)
 59         {
 60             if (!SAM.son[now][s[j]-'a']) {ans=min(ans,j-i+1); break;}
 61             now=SAM.son[now][s[j]-'a'];
 62         }
 63     }
 64     printf("%d\n",ans==2e9?-1:ans);
 65 }
 66 
 67 void Sub2()
 68 {
 69     int ans=2e9;
 70     for (int i=1; i<=slen; ++i)
 71     {
 72         int now=0;
 73         for (int j=i; j<=slen; ++j)
 74         {
 75             if (nextB[now][s[j]-'a']==-1) {ans=min(ans,j-i+1); break;}
 76             now=nextB[now][s[j]-'a'];
 77         }
 78     }
 79     printf("%d\n",ans==2e9?-1:ans);
 80 }
 81 
 82 void Sub3()
 83 {
 84     int ans=2e9;
 85     memset(len,0x7f,sizeof(len));
 86     len[1]=1;
 87     for (int i=1; i<=slen; ++i)
 88         for (int j=1; j<=SAM.cnt; ++j)
 89         {
 90             int nxt=SAM.son[j][s[i]-'a'];
 91             if (!nxt) ans=min(ans,len[j]);
 92             else len[nxt]=min(len[nxt],len[j]+1);
 93         }
 94     printf("%d\n",ans==2e9?-1:ans);
 95 }
 96 
 97 void Sub4()
 98 {
 99     int ans=2e9;
100     memset(len,0x7f,sizeof(len));
101     len[0]=0;
102     for (int i=1; i<=slen; ++i)
103         for (int j=tlen; j>=0; --j)
104         {
105             int nxt=nextB[j][s[i]-'a'];
106             if (nxt==-1) ans=min(ans,len[j]+1);
107             else len[nxt]=min(len[nxt],len[j]+1);
108         }
109     printf("%d\n",ans==2e9?-1:ans);
110 }
111 
112 int main()
113 {
114     scanf("%s%s",s+1,t+1);
115     slen=strlen(s+1), tlen=strlen(t+1);
116     for (int i=1; i<=tlen; ++i)
117         SAM.Insert(t[i]-'a');
118     CalcNext();
119     Sub1(); Sub2(); Sub3(); Sub4();
120 }

转载于:https://www.cnblogs.com/refun/p/10013106.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值