题目链接:
http://codeforces.com/gym/101194
题意:
给n个字符串s1,s2......sn,问只在第一个字符串中出现的最短子串,多个答案输出字典序最小的子串。
思路:
把n个串用'z'+1连接起来建立后缀自动机,把s2到sn的母串上所有位置都标记cnt=1,如果parent树的子树和求完后,某个状态的cnt==0,那么所有能到达该状态的串都只在s1中出现过,于是求从root到这些cnt==0的状态的字典序最小的最短路就好了。
Time:420ms
#include<bits/stdc++.h>
using namespace std;
const int N=600005;
int tot,root,last;
int pa[N],deep[N],ch[N][27],cnt[N];
int newnode(int _deep)
{
tot++;
memset(ch[tot],0,sizeof(ch[tot]));
deep[tot]=_deep;
cnt[tot]=0;
return tot;
}
void init()
{
tot=0;
root=newnode(0);
last=root;
}
void insert(int w)
{
int np=newnode(deep[last]+1);
int u=last;
while(u&&!ch[u][w]) ch[u][w]=np,u=pa[u];
if (!u) pa[np]=root;
else
{
int v=ch[u][w];
if (deep[u]+1==deep[v]) pa[np]=v;
else
{
int nv=newnode(deep[u]+1);
memcpy(ch[nv],ch[v],sizeof(ch[v]));
pa[nv]=pa[v]; pa[v]=pa[np]=nv;
while(u&&ch[u][w]==v) ch[u][w]=nv,u=pa[u];
}
}
last=np;
}
int sum[N],stk[N];
void topsort()
{
for (int i=0;i<=deep[last];i++) sum[i]=0;
for (int i=1;i<=tot;i++) sum[deep[i]]++;
for (int i=1;i<=deep[last];i++) sum[i]+=sum[i-1];
for (int i=1;i<=tot;i++) stk[sum[deep[i]]--]=i;
for (int i=tot;i>=2;i--) cnt[pa[stk[i]]]+=cnt[stk[i]];
}
bool vis[N];
bool fst[N];
char s[N];
char st[N];
int pre[N];
int pc[N];
int bfs(int S)
{
queue<int>Q;
vis[S]=1;
Q.push(S);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for (int w=0;w<26;w++)
{
int v=ch[u][w];
if (v&&fst[v]&&!vis[v])
{
pre[v]=u;
pc[v]=w;
if (cnt[v]==0) return v;
vis[v]=1;
Q.push(v);
}
}
}
return -1;
}
int main()
{
int t; scanf("%d",&t);
for (int cas=1;cas<=t;cas++)
{
init();
int n; scanf("%d",&n);
scanf("%s",st+1);
for (int i=1;st[i];i++) insert(st[i]-'a');
for (int i=2;i<=n;i++)
{
scanf("%s",s+1);
insert(26); cnt[last]=1;
for (int j=1;s[j];j++)
insert(s[j]-'a'),cnt[last]=1;
}
topsort();
for (int i=1;i<=tot;i++) vis[i]=fst[i]=0;
int now=1;
for (int i=1;st[i];i++)
{
now=ch[now][st[i]-'a'];
int tmp=now;
while(tmp&&!fst[tmp]) fst[tmp]=1,tmp=pa[tmp];
}
printf("Case #%d: ",cas);
int ret=bfs(1);
if (ret!=-1)
{
int cnt=0;
for (int i=ret;i!=1;i=pre[i]) s[++cnt]=pc[i]+'a';
for (int i=cnt;i>=1;i--) printf("%c",s[i]);
printf("\n");
}
else printf("Impossible\n");
}
}
后缀自动机求字符串s1以i结尾的子串和s2到sn连接起来的串的最长公共子串,这个子串再向左延伸一个字符就是以i结尾的最短的只在s1中出现的子串。求出答案后暴力找字典序最小的串,要是答案太多可能会超时,然而数据没有卡这个时间。
Time:358ms
#include<bits/stdc++.h>
using namespace std;
const int N=600005;
int tot,root,last;
int pa[N],deep[N],ch[N][27],cnt[N];
int newnode(int _deep)
{
tot++;
memset(ch[tot],0,sizeof(ch[tot]));
deep[tot]=_deep;
cnt[tot]=0;
return tot;
}
void init()
{
tot=0;
root=newnode(0);
last=root;
}
void insert(int w)
{
int np=newnode(deep[last]+1);
int u=last;
while(u&&!ch[u][w]) ch[u][w]=np,u=pa[u];
if (!u) pa[np]=root;
else
{
int v=ch[u][w];
if (deep[u]+1==deep[v]) pa[np]=v;
else
{
int nv=newnode(deep[u]+1);
memcpy(ch[nv],ch[v],sizeof(ch[v]));
pa[nv]=pa[v]; pa[v]=pa[np]=nv;
while(u&&ch[u][w]==v) ch[u][w]=nv,u=pa[u];
}
}
last=np;
}
int sum[N],stk[N];
void topsort()
{
for (int i=0;i<=deep[last];i++) sum[i]=0;
for (int i=1;i<=tot;i++) sum[deep[i]]++;
for (int i=1;i<=deep[last];i++) sum[i]+=sum[i-1];
for (int i=1;i<=tot;i++) stk[sum[deep[i]]--]=i;
for (int i=tot;i>=2;i--) cnt[pa[stk[i]]]+=cnt[stk[i]];
}
char s[N];
char st[N];
int f[N];
bool cmp(int p1,int p2,int len)
{
for (int i=0;i<len;i++)
if (st[p1+i]!=st[p2+i]) return st[p1+i]<st[p2+i];
return 1;
}
int main()
{
int t; scanf("%d",&t);
for (int cas=1;cas<=t;cas++)
{
init();
int n; scanf("%d",&n);
scanf("%s",st+1);
for (int i=2;i<=n;i++)
{
scanf("%s",s+1);
for (int j=1;s[j];j++)
insert(s[j]-'a');
insert(26);
}
int now=root,step=0;
int ans=N;
for (int i=1;st[i];i++)
{
while(now&&!ch[now][st[i]-'a']) now=pa[now];
if (!now) now=1,step=0;
else step=deep[now]+1,now=ch[now][st[i]-'a'];
f[i]=step;
if (i-f[i]>0) ans=min(ans,f[i]);
}
printf("Case #%d: ",cas);
if (ans==N) printf("Impossible\n");
else
{
int id=0;
for (int i=1;st[i];i++)
if (i-f[i]>0&&f[i]==ans)
{
if (id==0||cmp(i-ans,id-ans,ans+1)) id=i;
}
for (int i=id-f[id];i<=id;i++) printf("%c",st[i]);
printf("\n");
}
}
}
后缀数组+二分答案,每次判断当前前缀相同的块里是否有s1的后缀并且没有其它串的后缀
Time:483ms
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300005;
int a[N];
char s[N];
int pos,cut;
struct suffixarray
{
int sa[N],rk[N],h[N];
int cnt[N],sa2[N],rk2[N];
int rlen[N];
int n;
bool cmp(int i,int j,int len)
{
if (rk2[i]==rk2[j]&&rk2[i+len]==rk2[j+len]) return 0;
return 1;
}
void make(int *s,int len)
{
s[len+1]=-1; //按情况修改
n=len;
int m=60000; //按情况修改
for (int i=0;i<=m;i++) cnt[i]=0;
for (int i=1;i<=n;i++) cnt[s[i]]++;
for (int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for (int i=n;i>=1;i--) sa[cnt[s[i]]--]=i;
int k=1; rk[sa[1]]=1;
for (int i=2;i<=n;i++)
{
if (s[sa[i]]!=s[sa[i-1]]) k++;
rk[sa[i]]=k;
}
rk2[n+1]=0;
for (int j=1;k<n&&j<=n;j*=2)
{
int tot=0;
for (int i=n-j+1;i<=n;i++) sa2[++tot]=i;
for (int i=1;i<=n;i++)
if (sa[i]>j) sa2[++tot]=sa[i]-j;
m=k;
for (int i=1;i<=m;i++) cnt[i]=0;
for (int i=1;i<=n;i++) cnt[rk[i]]++;
for (int i=2;i<=m;i++) cnt[i]+=cnt[i-1];
for (int i=n;i>=1;i--) sa[ cnt[rk[sa2[i]]]-- ]=sa2[i];
for (int i=1;i<=n;i++) rk2[i]=rk[i];
k=1; rk[sa[1]]=1;
for (int i=2;i<=n;i++)
{
if (cmp(sa[i],sa[i-1],j)) k++;
rk[sa[i]]=k;
}
}
for (int i=1;i<=n;i++)
{
if (rk[i]==1)
{
h[rk[i]]=0; //这里很容易错
continue;
}
k=h[rk[i-1]];
if (k>0) k--;
int j=sa[rk[i]-1];
while(s[i+k]==s[j+k]) k++;
h[rk[i]]=k;
}
}
void show()
{
printf("\n");
for (int i=1;i<=n;i++)
{
for (int j=sa[i];j<=n;j++)
printf("%c",char(a[j]));
printf(" %d\n",h[i]);
}
}
int idx;
bool check(int len)
{
bool hv1=0,hv2=0;
for (int i=1;i<=n;i++)
{
if (h[i]<len)
{
if (hv1&&!hv2)
{
idx=sa[i-1];
return 1;
}
hv1=hv2=0;
if (rlen[sa[i]]>=len)
{
if (sa[i]<cut) hv1=1;
else hv2=1;
}
}
else
{
if (sa[i]<cut) hv1=1;
else hv2=1;
}
}
return 0;
}
void solve(int *s)
{
int tmp=n;
for (int i=n;i>=1;i--)
{
if (s[i]>'z') tmp=i,rlen[i]=0;
else rlen[i]=tmp-i;
}
int l=1,r=cut;
while(l<r)
{
int mid=(l+r)/2;
if (!check(mid)) l=mid+1;
else r=mid;
}
if (l==cut) printf("Impossible\n");
else
{
check(l);
for (int i=idx;i<idx+l;i++)
printf("%c",char(s[i]));
printf("\n");
}
}
}SA;
int main()
{
int t; scanf("%d",&t);
for (int cas=1;cas<=t;cas++)
{
int n; scanf("%d",&n);
pos=0;
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
for (int j=1;s[j];j++)
a[++pos]=s[j];
a[++pos]='z'+i;
if (i==1) cut=pos;
}
SA.make(a,pos);
printf("Case #%d: ",cas);
//SA.show();
SA.solve(a);
}
}