洛谷P2870 - [USACO07DEC]最佳牛线Best Cow Line

Portal

Description

给出一个字符串\(s(|s|\leq3\times10^4)\),每次从\(s\)的开头或结尾取出一个字符接在新字符串\(s'\)的末尾。求字典序最小的\(s'\)

Solution

设当前剩余的字符串为\(t\),将其翻转得到\(t'\)。则\(t<t'\)时取开头,否则取结尾。
\(p=lcp(t,t')\),则有\(t<t' \Leftrightarrow t[1..p]=t'[1..p],t[p+1]<t'[p+1]\)。那么最优的取法必然是依次取完\(t[1..p]\),也就是取开头。
s+'#'+rev(s)求后缀数组,那么可以将前半部分的后缀视为\(t\),后半部分的后缀视为\(t'\),比较\(rnk\)来决定取哪边。

时间复杂度\(O(nlogn)\)

Code

//[USACO07DEC]最佳牛线Best Cow Line
#include <cstdio>
#include <cstring>
int const N=6e4+10;
int n0,n; char s[N];
int sa[N],rnk[N<<1];
int cnt[N],tmp[N],rnk1[N<<1];
void getSA()
{
    for(int i=1;i<=n;i++) cnt[s[i]]=1;
    for(int i=1;i<=256;i++) cnt[i]+=cnt[i-1];
    for(int i=1;i<=n;i++) rnk[i]=cnt[s[i]];
    for(int L=1,k=0;k<n;L<<=1)
    {
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[i+L]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) tmp[cnt[rnk[i+L]]--]=i;
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
        k=0; memcpy(rnk1,rnk,sizeof rnk);
        for(int i=1;i<=n;i++)
        {
            if(rnk1[sa[i]]!=rnk1[sa[i-1]]||rnk1[sa[i]+L]!=rnk1[sa[i-1]+L]) k++;
            rnk[sa[i]]=k;
        }
    }
}
int main()
{
    scanf("%d",&n0); n=n0+n0+1;
    for(int i=1;i<=n0;i++) {char ch[5]; scanf("%s",ch),s[i]=ch[0];}
    s[n0+1]='#'; for(int i=1;i<=n0;i++) s[n-i+1]=s[i]; //puts(s+1);
    getSA();
    int L=1,R=n0+2;
    for(int i=1;i<=n0;i++)
    {
        printf("%c",rnk[L]<rnk[R]?s[L++]:s[R++]);
        if(i%80==0) puts("");
    }
    puts("");
    return 0;
}

P.S.

输出要求80个字符一行。

posted @ 2018-03-22 22:33  VisJiao  阅读(131)  评论(0编辑  收藏  举报