poj3581 Sequence(后缀数组sa的运用+离散化)

题目链接:点击打开链接

题意描述:给定一个整数序列,序列的第一个元素大于其余所有的元素,先把序列截成三段,然后对每一段进行逆转,求新生成的序列中字典序最小的序列?


解题思路:后缀数组sa的运用+离散化

分析:

先分析第一段:


如图所示表示目标序列,对第一段的长度进行讨论,其中红色1表示第一个元素,也就是最大值

1、如果上面三个序列在0~1区间不等,那么我们选取字典序较小的即可,否则我们在第1和第2个之间选取

2、如果前两个序列在1~2区间上不等,那么我们选取字典序较小的即可,否则我们选取第一个

从上述讨论我们发现对于第一段的选取我们需要选出逆序后字典序最小的即可采用后缀数组可以很容易解决


对于第二段和第三段的选取,我们可以把剩下的序列逆序重复两遍之后找字典序最小的即可

如x1~x2y1~y2z1~z2其中x1~x2已经确定,如果使剩下的最小呢?即:y2~y1z2~z1

我们只需要构造出这样的序列即可:z2~z1y2~y1z2~z1y2~y1

使用后缀数组sa可以很容易解决这个问题


代码:

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 400010;
int cmp(int *r,int a,int b,int l)
{
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}
int wa[N],wb[N],ww[N],wv[N];
void DA(int *r,int *sa,int n,int m)  //此处N比输入的N要多1,为人工添加的一个字符,用于避免CMP时越界
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0; i<m; i++) ww[i]=0;
    for(i=0; i<n; i++) ww[x[i]=r[i]]++;
    for(i=1; i<m; i++) ww[i]+=ww[i-1];
    for(i=n-1; i>=0; i--) sa[--ww[x[i]]]=i; //预处理长度为1
    for(j=1,p=1; p<n; j*=2,m=p) //通过已经求出的长度J的SA,来求2*J的SA
    {
        for(p=0,i=n-j; i<n; i++) y[p++]=i; // 特殊处理没有第二关键字的
        for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j; //利用长度J的,按第二关键字排序
        for(i=0; i<n; i++) wv[i]=x[y[i]];
        for(i=0; i<m; i++) ww[i]=0;
        for(i=0; i<n; i++) ww[wv[i]]++;
        for(i=1; i<m; i++) ww[i]+=ww[i-1];
        for(i=n-1; i>=0; i--) sa[--ww[wv[i]]]=y[i]; //基数排序部分
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;  //更新名次数组x[],注意判定相同的
    }
}
struct node
{
    int v,rk,id;
} st[N];
bool cmp1(node a,node b)
{
    return a.v<b.v;
}
bool cmp2(node a,node b)
{
    return a.id<b.id;
}
int n,str[N],sa[N],ans[N];
int main()
{
    scanf("%d",&n);
    for(int i=0; i<n; ++i){
        scanf("%d",&st[i].v);st[i].id=i;
    }
    sort(st,st+n,cmp1);
    int rk=1;
    st[0].rk=rk++;
    ans[st[0].rk]=st[0].v;
    for(int i=1; i<n; ++i){
        if(st[i].v==st[i-1].v) st[i].rk=st[i-1].rk;
        else{ st[i].rk=rk++;ans[st[i].rk]=st[i].v; }
    }
    sort(st,st+n,cmp2);
    for(int i=0; i<n-2; ++i)
        str[i]=st[n-2-i-1].rk;
    str[n-2]=0;
    DA(str,sa,n-1,rk);
    int len1=sa[1];
    for(int i=len1; i<n-2; ++i)
        printf("%d\n",ans[str[i]]);
    int m=len1+2;
    for(int i=0; i<m; i++)
        str[m-i-1]=str[2*m-i-1]=st[i+n-m].rk;
    n=m;
    str[2*n]=0;
    DA(str,sa,2*n+1,rk);///2*n+1
    int x;
    for(int i=1; i<=2*n; ++i)///问题1
        if(sa[i]>0&&sa[i]<n){ x=sa[i];break;}
    for(int i=0; i<n; ++i) printf("%d\n",ans[str[x+i]]);
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值