2016 China Final F Mr. Panda and Fantastic Beasts

2016 China Final Or Gym - 101194F F Mr. Panda and Fantastic Beasts

题目链接:

http://codeforces.com/gym/101194

题意:

给定N个只包含小写字母的字符串,然后要求输出最短的字符串满足只在第一个串中出现,而不是其他字符串的子串,如果有多个这样的字符串,输出字典序最小的字符串

思路:

看题解有后缀数组、后缀自动机、二分加hash的做法,这里我先学了后缀数组的做法,其他的做法暂时还不会。

首先我们将所有的字符串合并成一个字符串,然后字符串之间插入不同的不在原字符串中的字符,这样就可以将不同的字符串区分开来,由于我们是要找到一个在第一个字符串中出现但是在其他字符串中不出现的子串。

我们可以这样考虑,首先枚举第一个字符串中的每一个后缀,我们所求的字符串假设是该后缀的前缀,那么我们知道我们所求的字符串应该不是除第一个字符串的后缀的前缀,由于我们要求的是最短的该字符串,我们可以先求出我们枚举的后缀和除第一个字符串的后缀的最大前缀的最大值,这里代表的就是如果我们的答案字符串是这个最大前缀的话,那么就会出现这个答案字符串不仅出现的第一个字符串里面,而且还出现在其他字符串里面,不符合要求,但是我们只要在这个字符串后面再添上一个字符,就可以保证不会再出现违反题目要求的情况,当然我们要保证添上一个字符还是第一个字符串的子串,而不会包含字符串之间的分隔符

那么我们现在要解决的问题就是在枚举了第一个字符串的后缀之后如何求它与除第一个字符串的后缀的最大前缀,这里我们如果已经构造出了后缀数组已经它对应的height数组。我们知道如果不在第一个字符串的后缀在sa数组中越接近我们所枚举的第一个字符串的后缀,那么它们的最大公共前缀就越大,所以我们只需要往左边后右边找,找到第一个不在第一个字符串的后缀,然后分别求它们和枚举的后缀的LCP(最大公共前缀)长度,然后这个答案+1就是我们可行的一个答案字符串(注意不要超过第一个字符串的长度),然后对于所有的答案字符串求个长度最小的就可以了,由于答案要求长度相同时字典序最小,所以在枚举后缀的时候按sa数组枚举就好了,代码实现和思路参考了2016 China-Final 解题报告

代码:

#define debug //printf
// F.cpp
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int maxn=310000+100;
int T,N;
int kase;
char str[maxn];
const int INF=1e9;
struct SuffixArray{
    int s[maxn];
    int sa[maxn];
    int ran[maxn];
    int height[maxn];
    int t[maxn],t2[maxn],c[maxn];
    int n;
    void clear()
    {
        n=0;memset(sa,0,sizeof(sa));
    }

    void build_sa(int m)
    {
        int i,*x=t,*y=t2;
        for(i=0;i<m;i++)    c[i]=0;
        for(i=0;i<n;i++)    c[x[i]=s[i]]++;
        for(i=1;i<m;i++)    c[i]+=c[i-1];
        for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
        for(int k=1;k<=n;k<<=1){
            int p=0;
            for(i=n-k;i<n;i++)  y[p++]=i;
            for(i=0;i<n;i++)    if(sa[i]>=k)    y[p++]=sa[i]-k;
            for(i=0;i<m;i++)    c[i]=0;
            for(i=0;i<n;i++)    c[x[y[i]]]++;
            for(i=1;i<m;i++)    c[i]+=c[i-1];
            for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
            swap(x,y);
            p=1;x[sa[0]]=0;
            for(i=1;i<n;i++)
                x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
                if(p>=n)    break;
                m=p;
        }
    }

    void build_height()
    {
        int i,j,k=0;
        for(i=0;i<n;i++){
            ran[sa[i]]=i;
        }
        for(i=0;i<n;i++){
            if(k)   k--;
            int j=sa[ran[i]-1];
            while(s[i+k]==s[j+k])   k++;
            height[ran[i]]=k;
        }
    }
};

SuffixArray sa;

void print_sub(int L,int R)
{
    for(int i=L;i<R;i++)
        printf("%c",sa.s[i]-1+'a');
    printf("\n");
}

void add(int ch)
{
    sa.s[sa.n++]=ch;
}

void solve();

int main()
{
    scanf("%d",&T);
    for(kase=1;kase<=T;kase++){
        solve();
    }
    return 0;
}

void solve()
{
    sa.clear();
    scanf("%d",&N);
    int limt=0;
    for(int i=1;i<=N;i++){
        scanf("%s",str);
        int len=strlen(str);
        if(i==1) limt=len;
        for(int j=0;j<len;j++){
            add(str[j]-'a'+1);
        }
        add(27+i);
    }
    add(0);
    sa.build_sa(28+N);
    sa.build_height();
    int tmpn=sa.n-1;
    int ans=INF,pos=-1;
    for(int i=1;i<=tmpn;i++){
        while(i<=tmpn&&sa.sa[i]>=limt)  i++;
        if(i>tmpn)  break;
        int cur[2];
        memset(cur,0,sizeof(cur));
        cur[0]=sa.height[i];
        if(i==tmpn)
            cur[1]=0;
        else
            cur[1]=sa.height[i+1];
        for(int j=i-1;j&&sa.sa[j]<limt;j--){
            cur[0]=min(cur[0],sa.height[j]);
        }
        for(int j=i+1;j<tmpn&&sa.sa[j]<limt;j++){
            cur[1]=min(cur[1],sa.height[j+1]);
        }
        cur[0]=max(cur[0],cur[1])+1;
        if(cur[0]+sa.sa[i]>limt)    continue;
        if(cur[0]<ans){
            ans=cur[0];
            pos=sa.sa[i];
        }
    }
    printf("Case #%d: ",kase);
    if(ans<=limt)
        print_sub(pos,pos+ans);
    else puts("Impossible");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值