4032: [HEOI2015]最短不公共子串
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 430 Solved: 209
[ Submit][ Status][ Discuss]
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
abcabc
Sample Output
2
4
2
4
4
2
4
HINT
对于100%的数据,A和B的长度都不超过2000
Source
题解:DP+后缀自动机
四个问题写了四个部分分,我也是醉了。。。
这道题的第一问我竟然被卡hash了!!!人生第一次被卡hash。。。还是老老实实的DP吧。
其实只有第三问需要用后缀自动机。。。。刚开始还死往那上面想。
四个部分的题解都在程序的注释中,懒得再写一遍了
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<map>
#define N 20013
#define p1 2000001001
#define ull unsigned long long
using namespace std;
int cnt,n,m,np,p,q,nq,l[N],r[N],pos[N],root;
int ch[N][30],fa[N],len[N],last,ch1[N][30],f[2003][2003];
ull mi[N];
char s[N],s1[N];
map<int,int> mp;
map<ull,int> mp1;
void extend(int i)
{
int c=s1[i]-'a'+1;
p=last; np=last=++cnt; l[np]=l[p]+1;
for (;!ch[p][c]&&p;p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=root;
else {
q=ch[p][c];
if (l[q]==l[p]+1) fa[np]=q;
else {
nq=++cnt; l[nq]=l[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void solve1()
{
/*mi[0]=1;
for (int i=1;i<=max(n,m)+2;i++) mi[i]=mi[i-1]*p1;
for (int i=1;i<=m;i++){
ull x=(ull)s1[i]*mi[1]; mp1[x]=1;
//cout<<x<<endl;
for (int j=i+1;j<=m;j++) {
x=x*p1+s1[j]*mi[i];
mp1[x]=(j-i+1);
// cout<<x<<endl;
}
}
int mn=m+1;
for (int i=1;i<=n;i++) {
ull x=(ull)s[i]*mi[1];
if (!mp1[x]) mn=min(mn,1);
for (int j=i+1;j<=n;j++){
x=x*p1+s[j]*mi[1];
if (!mp1[x]&&j-i+1<=n) mn=min(mn,j-i+1);
}
}
if (mn>=m+1) printf("-1\n");
else printf("%d\n",mn);*/
memset(f,0,sizeof(f));
int mn=m+1;
for (int i=1;i<=n;i++){
int a=0,b=0;
for (int j=1;j<=n;j++) {
if (s[i]==s1[j]) f[i][j]=f[i-1][j-1]+1;
a=max(f[i][j],a);
}
if (a!=i) mn=min(mn,a);
}
if (mn>=m+1) printf("-1\n");
else printf("%d\n",mn+1);
}
void solve2()//A的最短子串,不是B的子序列
{
memset(f,0,sizeof(f));
int mn=m+1;
for (int i=1;i<=n;i++){
int a=0,b=0;
for (int j=1;j<=m;j++) {
if (s[i]==s1[j]) f[i][j]=b+1;
a=max(a,f[i][j]);
b=max(b,f[i-1][j]); //f[i][j]表示A中的第i个字符匹配到B中第j个字符,A匹配时必须连续
}
if (a!=i) mn=min(mn,a);//匹配的长度不是i说明,在i+前一字符就是一个合法的子串
}
if (mn>=m+1) printf("-1\n");
else printf("%d\n",mn+1);
}
void solve3()//A的最短的子序列,不是b的子串。
{
memset(len,127/3,sizeof(len));
len[1]=0; int mn=m+1; int t;
for (int i=1;i<=n;i++)
for (int j=1;j<=cnt;j++)
if (!(t=ch[j][s[i]-'a'+1])) mn=min(mn,len[j]);//如果到该点匹配不上了,说明在当前基础上加入s[i]就能得到了一个合法的子序列
else len[t]=min(len[t],len[j]+1);//len表示的是用a的子序列去匹配后缀自动机中的节点,到节点i能得到的最短长度
if (mn==m+1) printf("-1\n");
else printf("%d\n",mn+1);
}
void solve4()
{
for (int i=m;i>=0;i--){//只要是在当前位置之后出现的字符都可以用来构造子序列,如果我们要选择字符x,那么必然优先选择最靠近当前位置的字符。
for (int j=1;j<=26;j++)
if (mp[j]) ch1[i][j]=mp[j];
mp[s1[i]-'a'+1]=i;
}
memset(len,127/3,sizeof(len));
len[0]=0;
int mn=m+1; int t;
for (int i=1;i<=n;i++)
for (int j=m;j>=0;j--)//这里与第三问不同,必须倒序枚举,因为这里相当于是个背包,那么对于一个字符来说,不能连续更新同一层中的位置,否则原串只有一个字符x,正序枚举会出现xx的情况。
if (!(t=ch1[j][s[i]-'a'+1])) mn=min(mn,len[j]);
else len[t]=min(len[t],len[j]+1);
if (mn==m+1) printf("-1\n");
else printf("%d\n",mn+1);
}
int main()
{
freopen("sus4.in","r",stdin);
//freopen("sus.out","w",stdout);
scanf("%s",s+1);
scanf("%s",s1+1);
n=strlen(s+1); m=strlen(s1+1);
root=last=++cnt;
for (int i=1;i<=m;i++)
extend(i);
solve1();
solve2();
solve3();
solve4();
}