POJ 3581 Sequence(后缀数组)
【题目链接】 http://poj.org/problem?id=3581
【题目大意】
给出一个数列,将这个数列分成三段,每段分别翻转,使得其字典序最小,输出翻转后的数列。
【题解】
首先,第一个翻转点就是翻转后数列的最小后缀,注意由于一定要分成三段,则至少要剩下两个元素。难点主要是如何处理第二个翻转点,我们发现剩余的部分的每一种翻转拆分都是将两串翻转后剩余部分拼接在一起得到的串的子串,所以我们将剩余部分翻转,复制一份拼接在后面,求最小后缀即可。
【代码】
#include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int N=800010; int n,rank[N],sa[N],h[N],tmp[N],cnt[N],ans; int s[N]; void suffixarray(int n,int m){ int i,j,k;n++; for(i=0;i<2*n+5;i++)rank[i]=sa[i]=h[i]=tmp[i]=0; for(i=0;i<m;i++)cnt[i]=0; for(i=0;i<n;i++)cnt[rank[i]=s[i]]++; for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; for(i=0;i<n;i++)sa[--cnt[rank[i]]]=i; for(k=1;k<=n;k<<=1){ for(i=0;i<n;i++){ j=sa[i]-k; if(j<0)j+=n; tmp[cnt[rank[j]]++]=j; }sa[tmp[cnt[0]=0]]=j=0; for(i=1;i<n;i++){ if(rank[tmp[i]]!=rank[tmp[i-1]]||rank[tmp[i]+k]!=rank[tmp[i-1]+k])cnt[++j]=i; sa[tmp[i]]=j; }memcpy(rank,sa,n*sizeof(int)); memcpy(sa,tmp,n*sizeof(int)); if(j>=n-1)break; }for(j=rank[h[i=k=0]=0];i<n-1;i++,k++) while(~k&&s[i]!=s[sa[j-1]+k])h[j]=k--,j=rank[sa[j]+1]; } int disc[N]; int remark(int x){ int l=1,r=n; while(l<=r){ int mid=(l+r)>>1; if(disc[mid]<x)l=mid+1; else if(disc[mid]==x)return mid; else r=mid-1; } } int a[N],p1,p2; int main(){ scanf("%d",&n); for(int i=0;i<n;i++)scanf("%d",a+i),disc[i+1]=a[i]; sort(disc+1,disc+n+1); for(int i=0;i<n;i++)a[i]=remark(a[i]); reverse_copy(a,a+n,s); suffixarray(n,n+1); for(int i=0;i<=n;i++){p1=n-sa[i];if(p1>=1&&n-p1>=2)break;} int m=n-p1; reverse_copy(a+p1,a+n,s); reverse_copy(a+p1,a+n,s+m); suffixarray(m<<1,n+1); for(int i=0;i<=2*m;i++){p2=p1+m-sa[i];if(p2>p1&&n>p2)break;} reverse(a,a+p1);reverse(a+p1,a+p2);reverse(a+p2,a+n); for(int i=0;i<n;i++)printf("%d\n",disc[a[i]]); return 0; }