链接:点击打开链接
题意:给出N个数字组成的数列,将这N个数字分成三段并反转,求能得到的字典序最小的数列是什么(第一个数大于数列中的任意一个数)
代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,k;
int rank[200005],tmp[200005];
bool cmp(int i,int j){
int ri,rj;
if(rank[i]!=rank[j])
return rank[i]<rank[j];
ri=i+k<=n?rank[i+k]:-1;
rj=j+k<=n?rank[j+k]:-1;
return ri<rj;
}
void get_suffix(int *s,int *sa){
int i;
// n=s.length();
for(i=0;i<=n;i++){
sa[i]=i;
rank[i]=i<n?s[i]:-1;
}
for(k=1;k<=n;k*=2){
sort(sa,sa+n,cmp);
tmp[sa[0]]=0;
for(i=1;i<=n;i++)
tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i])?1:0);
for(i=0;i<=n;i++)
rank[i]=tmp[i];
}
} //后缀数组模板
int s[200005],rev[200005],sa[200005];
int main(){
int i,j,ans1,ans2,tmp_n,temp;
scanf("%d",&n);
temp=n;
for(i=0;i<n;i++)
scanf("%d",&s[i]);
reverse_copy(s,s+n,rev); //先将所有数字反转,第一段就是反转后后缀数组最小的
get_suffix(rev,sa);
for(i=0;i<n;i++){
ans1=n-sa[i];
if(ans1>=1&&n-ans1>=2) //判断是否还能分成两段
break;
}
reverse_copy(s+ans1,s+n,rev); //将剩下的所有数字复制两次,如果直接找剩下的最小后
reverse_copy(s+ans1,s+n,rev+n-ans1);//数组则会在一些数据上产生错误,例如6 10 1 2 2 3 4
n=2*(n-ans1); //然后改变数组的长度n
get_suffix(rev,sa);
for(i=0;i<n;i++)
if(sa[i]>=0&&sa[i]<n/2){
ans2=n/2-sa[i]+ans1;
if(ans2-ans1>=1&&temp-ans2>=1) //判断是否还能反转
break;
}
reverse(s,s+ans1);
reverse(s+ans1,s+ans2);
reverse(s+ans2,s+temp);
for(i=0;i<temp;i++)
printf("%d\n",s[i]);
return 0;
}