首先,这是一道贪心题。
先来看一个简易版本的题目: 给你两个字符串S1 S2,答案串最开始是空的, 每次都可以从两个字符串开头拿一个字符到答案串末尾。直到两个字符串全部取完。 现在要求一个操作序列。 使得答案串的字典序最小。
这显然是一个贪心的过程。 如果s1串的首字符小于s2串首字符,则取s1的首字符,反之,则取s2串的首字符。。。 到此为止的贪心都是很显然的, 问题在于当s1首字符和s2首字符相同时又该如何选取呢。。 这是不能随便选的(废话)。 这时候一个很显然的想法就是比较当前s1串和当前s2串的字典序大小。 若s1小于s2,则取s1首字符,反之,则取s2首字符。 那两个字典序一样大呢? 随便取就行了(因为这时候s1和s2是相同的)
这道题无非就是s2是s1的逆序串。。 想清楚策略之后就是SA裸题了
#include <iostream>
#include <cstdio>
#define ws wss
#define rank rankk
#define MAXN 100000
using namespace std;
int n,m;
char ans[MAXN];
char c[MAXN];
int t1[MAXN],t2[MAXN],r[MAXN],sa[MAXN],rank[MAXN],ws[MAXN];
int cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int n,int m)
{
int *x=t1,*y=t2,i,j,p;
for (i=0;i<m;++i) ws[i]=0;
for (i=0;i<n;++i) ws[x[i]=r[i]]++;
for (i=1;i<m;++i) ws[i]+=ws[i-1];
for (i=n-1;i>=0;--i) sa[--ws[x[i]]]=i;
for (j=1,p=1;p<n;j*=2,m=p)
{
for (i=n-j,p=0;i<n;++i) y[p++]=i;
for (i=0;i<n;++i) if (sa[i]>=j) y[p++]=sa[i]-j;
for (i=0;i<m;++i) ws[i]=0;
for (i=0;i<n;++i) ws[x[y[i]]]++;
for (i=1;i<m;++i) ws[i]+=ws[i-1];
for (i=n-1;i>=0;--i) sa[--ws[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=0;
for (i=1,p=1;i<n;++i)
x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
}
}
inline int f(int x)
{
return rank[x];
}
inline int g(int x)
{
x=2*m-x;
return rank[x];
}
int main()
{
cin>>n;
m=n;
for (int i=0;i<n;++i)
{
while (isspace(c[i]=getchar())) ;
r[i]=(int)c[i];
}
r[n]=1;
for (int i=n+1;i<=2*n;++i) r[i]=r[2*n-i];
r[2*n+1]=0;
n=2*n+2;
da(r,n,200);
for (int i=1;i<n;++i) rank[sa[i]]=i;
int l=0,r=m-1;
for (int i=1;i<=m;++i)
{
if (f(l)<g(r)) {ans[i]=c[l];++l;}
else {ans[i]=c[r];--r;}
}
for (int i=1;i<=m;++i)
{
putchar(ans[i]);
if (i%80==0) putchar('\n');
}
return 0;
}