poj3581后缀数组+离散化思想

今天做后缀数组练习时,偶遇poj3581,这题并不难,但是感觉思路比较巧妙,而且学习了一下离散化的思想,所以做一下记录


链接:http://poj.org/problem?id=3581


题目大意是给你一个串,你把它分成三段,并且把每一段翻转,求字典序最小


一开始我的想法是:从头到Rank最小的后缀为一段,拿掉这一段后,又从断口到Rank最小的后缀为一段,剩下的为一段


但是很快就发现这个思路是不正确的,以5 0 3 1 2 3 1 4为例,他对应的sa[1~8]数组结果是1 3 6 4 2 5 7 0,那么,根据我的思路得到的最终结果就是0 5 1 3 4 1 3 2,而正确的结果应该是0 5 1 3 2 1 3 4,问题就出在当串中存在相同值的情况,这是应该结合前后值来判断


我借鉴了网上的思路:http://blog.163.com/just_gogo/blog/static/1914390652011823103842787/

稍微分析一下:首先获得第一个下标肯定是字典序最小的那个没有疑问,关键就在于第二个下标,如果把第一部分去掉之后,还存在相同的值,可能存在一个后缀由于元素个数少而字典序靠前,经过翻转处理之后,可能后面又接上其他元素,导致字典序变大,所以在处理第二个下标时,应该把前面的元素贴到后面来做成新的串,然后在求sa数组,从而获得第二个下标(语文没学好,解释不清。。。)


除此以外,这道题还教会了我离散化的思想,我简单的说一下:就是当数据的上下限没给,只有数据个数时,可以通过结构体加排序,用排名来代替数值。


代码实现:


#include<stdio.h>
#include<algorithm>
using namespace std;
int str[200005],sa[200005],a[200005],b[200005],c[200005],index1,index2,n;
struct NUM//利用结构体离散化
{
    int id,val,Rank;
}num[200005];
bool cmp1(const NUM &a,const NUM &b)//按值排序
{
    return b.val>a.val;
}
bool cmp2(const NUM &a,const NUM &b)//按输入顺序排序
{
    return b.id>a.id;
}
void getRank(int top)
{
    int p=2;
    num[0].Rank=1;
    for(int i=1;i<top;i++)
        num[i].Rank=(num[i].val==num[i-1].val)?p-1:p++;
}
void getStr(int top)
{
    for(int i=0;i<top;i++)
        str[top-1-num[i].id]=num[i].Rank;
}
void DA(int top,int m)
{
    int *x=a,*y=b;
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<top;i++)
        c[x[i]=str[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=top-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=top;k*=2)
    {
        int p=0;
        for(int i=top-k;i<top;i++)
            y[p++]=i;
        for(int i=0;i<top;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<top;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=top-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<top;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p-1:p++;
        if(p>=top)
            break;
        m=p;
    }
}
int update(int index)
{
    int top=index;
    for(int i=0;i<index;i++)//把前面的值贴到后面
        str[top++]=str[i];
    str[top]=0;//补零准备DA
    return top;
}
void show(int index,int top)
{
    for(int i=index;i<top;i++)
        printf("%d\n",num[n-1-i].val);
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&num[i].val);
        num[i].id=i;
    }
    sort(num,num+n,cmp1);//排序获得Rank(离散化),用Rank获得sa
    getRank(n);
    getStr(n);
    str[n]=0;
    sort(num,num+n,cmp2);//还原数据顺序
    DA(n+1,200002);
    index1=sa[1]>1?sa[1]:(sa[2]>1?sa[2]:sa[3]);//第一个下标要保证还剩下2个及以上的数据
    show(index1,n);
    int temp=update(index1);//利用index1更新一下str
    DA(temp+1,200002);
    for(int i=0;;i++)//第二个下标要保证还剩下1个及以上的数据,而且肯定在index1之前,因为其他都是补上去的
    {
        if(sa[i]>=index1||sa[i]==0)
            continue;
        else
        {
            index2=sa[i];
            break;
        }
    }
    show(index2,index1);
    show(0,index2);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值