题目链接
https://vjudge.net/problem/POJ-3581
题意
给定N个数字组成的序列A1,A2,…,An.其中A1比其他数字都大。现在要把这个序列分成三段,并将每段分别反转,求能得到的字典序最小的序列是什么?要求分得的每段不为空。
限制条件:N<=200000.
分析
因为A1最大,所有将原串反转后求后缀数组,字典序最小的合适的子串就是第一段。
然后将剩下的子串重复一遍再反转,并求其后缀数组,字典序最小的合适的子串就是第二段。
3500ms的代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=2e5+100;
int n,k;
int rank[maxn+1],sa[maxn+1],b[maxn];
int tmp[maxn+1];
int a[maxn];
//比较(rank[i],rank[i + k] )和 ( rank[j],rank[j+k] )
bool compare_sa(int i,int j)
{
if(rank[i]!=rank[j])
return rank[i]<rank[j];
else
{
int ri=i+k<=n?rank[i+k]:-1;
int rj=j+k<=n?rank[j+k]:-1;
return ri<rj;
}
}
void construct_sa(int s[])
{
//n=sizeof(s)/sizeof(int);
//初始化长度为1,rank直接取字符的编码
for(int i=0; i<=n; i++)
{
sa[i]=i; //把sa看作字符串
rank[i]=i<n?s[i]:-1; //rank看作字符串的排名
}
//利用对长度为k的排序的结果对长度为2k的排序
for(k=1; k<=n; k*=2)
{
sort(sa,sa+n+1,compare_sa);
//先在tmp中临时存储新计算的rank,再转存回rank中
tmp[sa[0]]=0;
for(int i=1; i<=n; i++)
{
tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0); //因为相同字符rank相同,所以不能直接按sort后的结果来安排rank
}
for(int i=0; i<=n; i++)
rank[i]=tmp[i];
}
}
int main()
{
int N;
scanf("%d",&N);
//输入
for(int i=0; i<N; i++)
scanf("%d",&a[i]);
//将原串反转
reverse_copy(a,a+N,b);
//求后缀数组
n=N;
construct_sa(b);
//字典序最小的合适的子串就是第一段
int p1;
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,b);
reverse_copy(a+p1,a+N,b+m);
//求后缀数组
n=m*2;
construct_sa(b);
//字典序最小的合适的子串就是第二段
int p2;
for(int i=0; i<=2*m; i++)
{
p2=p1+m-sa[i];
if(p2>=p1+1 && p2<N)
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",a[i]);
return 0;
}