1692: [Usaco2007 Dec]队列变换 后缀数组+贪心

边做这题边在怀疑人生,我去年是怎么学会后缀数组的。。为什么现在看着一(九)脸茫然QAQ。
这题首先贪心策略显然:每次从头或尾取一个较小的加入。然后对于头尾相同的,我们应该判断下一位的大小。而如果暴搜的话,估计AAAAAAAAAAAQAAAAAAAAAAA的数据会被卡成dog。
我们需要快速判断一个后缀和一个前缀的大小。
比如AABCAA:
用f[i]表示从第i个开始整个串的后缀(比如f[1] = AABCAA)
用g[i]表示从第i个开始整个串的前缀(比如g[4] = CBAA)
那么当我们遇到相同的情况下,比较f[L]和g[R],若f[L]>g[R]则证明后者更优。
维护后缀的关系考虑后缀数组,而前缀只要把原串反写一下转化为求后缀就好了。最后根据rank数组判断。

注意:cc数组不要开小了QAQ因此WA了两发

#include<bits/stdc++.h>
using namespace std;
int len;
int cc[60005],t1[60005],t2[60005],sa[60005],rank[60005];
char s[60005];
inline char read()
{
    char c=getchar();
    while (c<'A'||c>'Z') c=getchar();
    return c;
}
inline bool cmp(int *y,int a,int b,int l)
{
    int arank1=y[a];
    int brank1=y[b];
    int arank2=a+l>=len?-1:y[a+l];
    int brank2=b+l>=len?-1:y[b+l];
    return arank1==brank1&&arank2==brank2;
}
inline void make_sa()
{
    int *x=t1,*y=t2;
    int m=27;
    for (int i=0;i<m;i++) cc[i]=0;
    for (int i=0;i<len;i++) ++cc[x[i]=s[i]];
    for (int i=1;i<m;i++) cc[i]+=cc[i-1];
    for (int i=len-1;i>=0;i--) sa[--cc[x[i]]]=i;
    for (int k=1;k<len;k<<=1)
    {
        int p=0;
        for (int i=len-k;i<len;i++) y[p++]=i;
        for (int i=0;i<len;i++)
            if (sa[i]>=k) y[p++]=sa[i]-k;
        for (int i=0;i<m;i++) cc[i]=0;
        for (int i=0;i<len;i++) ++cc[x[y[i]]];
        for (int i=1;i<m;i++) cc[i]+=cc[i-1];
        for (int i=len-1;i>=0;i--) sa[--cc[x[y[i]]]]=y[i];
        swap(x,y);
        m=1; x[sa[0]]=0;
        for (int i=1;i<len;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?m-1:m++;
        if (m>=len) break;
    }
}       
int main()
{
    scanf("%d",&len);
    for (int i=0;i<len;i++) s[i]=read();
    for (int i=0;i<len;i++) s[i]-=('A'-1);
    s[len]=0;
    for (int i=1;i<=len;i++) s[len+i]=s[len-i];
    len=len*2+1;
    make_sa();
    for (int i=0;i<len;i++) rank[sa[i]]=i;
    int L=0,R=len/2+1;
    for (int i=1;i<=(len>>1);i++)
    {
        if (rank[L]<rank[R]) putchar(s[L++]+'A'-1);
        else putchar(s[R++]+'A'-1);
        if (!(i%80)) puts("");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值