2016 ACM-ICPC China Finals #F Mr. Panda and Fantastic Beasts

题目链接$\newcommand{\LCP}{\mathrm{LCP}}\newcommand{\suf}{\mathrm{suf}}$

题意

给定 $n$ 个字符串 $s_1, s_2, \dots, s_n$,求只在 $s_1$ 中出现过的最短子串,若有多解,输出字典序最小的。

分析

为了方便, 称只在 $s_1$ 中出现过的子串为「特殊子串」,记「字符串 $s$ 是字符串 $t$ 的子串」作 $ s \sqsubseteq t$ 。

引理 1
若 $s'$ 是特殊子串,若字符串 $s$ 满足 $s \sqsubseteq s_1$ 且 $s' \sqsubseteq s$,那么 $s$ 也是特殊子串。

因此可二分答案。

问题化为: 判断是否存在长为 $x~(x \le |s_1|)$ 的特殊子串.

用后缀数组解决上述判定问题

  1. 将 $s_1, s_2, \dots, s_n$ 用 未在原串中出现过各不相同 的字符链接成一个长串 $s$ , 构造 $s$ 的后缀数组 $\suf_1, \suf_2, \dots, \suf_i, \dots$ 和 height 数组 $h_1, h_2, \dots, h_i, \dots$ .
  2. 寻找后缀数组中满足如下条件的连续段 $\suf_i, \suf_{i+1}, \dots, \suf_j, \dots, \suf_{i+k-1}$.
    1. $\suf_j$ 起始于 $s_1$ 中
    2. $|\suf_j \sqcap s_1| \ge x$
    3. $|\LCP(\suf_{j_1}, \suf_{j_2})| \ge x$, $\LCP(s, t)$ 指字符串 $s, t$ 的最长公共前缀 (Longest Common Prefix)
    4. $h_i<x$ 且 $h_{i+k}<x$

存在长为 $x$ 的特殊子串 $\Longleftrightarrow$ 存在上述连续段

Implementation

#include <bits/stdc++.h>
using namespace std;

const int N=3e5+5;
char s[N];
int a[N];
int suf[N], suf2[N], rk[N], cnt[N], h[N];

void SA(int *s, int n, int m, int *suf, int *suf2, int *rk, int *cnt){
    for(int i=0; i<m; i++)
        cnt[i]=0;
    for(int i=0; i<n; i++)
        cnt[rk[i]=s[i]]++;
    for(int i=1; i<m; i++)
        cnt[i]+=cnt[i-1];
    for(int i=n-1; i>=0; i--)
        suf[--cnt[rk[i]]]=i;

    for(int len=1; len<n; len<<=1){
        int tail=0;
        for(int i=n-len; i<n; i++)
            suf2[tail++]=i;
        for(int i=0; i<n; i++)
            if(suf[i]>=len) suf2[tail++]=suf[i]-len;

        for(int i=0; i<m; i++)
            cnt[i]=0;
        for(int i=0; i<n; i++)
            cnt[rk[i]]++;
        for(int i=1; i<m; i++)
            cnt[i]+=cnt[i-1];
        for(int i=n-1; i>=0; i--)
            suf[--cnt[rk[suf2[i]]]]=suf2[i];

        swap(suf2, rk);
        auto same=[suf2, len](int i, int j){
            return suf2[i]==suf2[j] && suf2[i+len]==suf2[j+len];
        };

        rk[suf[0]]=0;
        for(int i=1; i<n; i++)
            rk[suf[i]]=rk[suf[i-1]]+!same(suf[i], suf[i-1]);

        m=rk[suf[n-1]]+1;
        if(m==n) break;
    }
}

void calc(int *a, int *suf, int n, int *rk, int *h){
    for(int i=0; i<n; i++)
        rk[suf[i]]=i;

    for(int i=0, lcp=0; i<n-1; i++){
        if(lcp) --lcp;
        for(int j=suf[rk[i]-1]; a[j+lcp]==a[i+lcp]; lcp++);
        h[rk[i]]=lcp;
    }
}

int ok(int x, int len0, int len, int *h){
    //two-pointers
    for(int i=1; i<=len; i++){  //tricky
        if(suf[i]<len0 && suf[i]+x <= len0 && h[i]<x){  //error-prone
            for(++i; i<=len && h[i]>=x && suf[i]<len0; i++);
            if(i>len || h[i]<x){
                assert(suf[i-1]>=0);
                return suf[i-1];
            }
        }
    }
    return -1;
}

int main(){
    int T;
    cin>>T;

    for(int cas=1; cas<=T; cas++){
        printf("Case #%d: ", cas);
        int n;
        cin>>n;
        cin>>s;
        int len0=strlen(s), len=len0;

        for(int i=1; i<n; i++){
            s[len++]='#';
            cin>>(s+len);
            len+=strlen(s+len);
        }

        int m=256;

        for(int i=0; i<=len; i++){
            if(s[i]=='#') a[i]=m++;
            else a[i]=s[i];
        }

        SA(a, len+1, m, suf, suf2, rk, cnt);
        calc(a, suf, len+1, rk, h);


        int l=0, r=len0+1, res=-1;
        for(; l+1<r; ){
            int mid=(l+r)>>1;
            int tmp=ok(mid, len0, len, h);
            if(tmp!=-1){
                res=tmp;
                r=mid;
            }
            else l=mid;
        }

        if(r>len0) puts("Impossible");
        else{
            for(int i=res; i<res+r; i++)
                putchar(s[i]);
            puts("");
        }
    }

    return 0;
}

转载于:https://www.cnblogs.com/Patt/p/6354086.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值