[ BZOJ 2882 ] 工艺
\(\\\)
Description
求一个串的所有循环同构串里字典序最小的一个。
- \(n\le 3\times 10^5\)
\(\\\)
Solution
本来是找 SA 题找到这道的......
首先 SA 的做法已经烂大街了,就是复制一遍求 \(rank\) ,取 \(rank\) 最高的长度 \(\ge n\) 的后缀的前缀即可。
后来突然想起来 \(lyd\) 的书上好像有个最小表示法是用来搞这个的,然后就去 % 了一发书.......
最小表示法基于字典序的定义。
首先将串复制一倍接在后面,显然它包含所有循环同构串。
我们设当前比较的两个循环同构串,在大串里头指针分别是 \(i,j\) 。
-
假如比到某一位置,发现 \(s[i+k]<s[j+k]\) :
此时显然以 \(j\) 开始不是最优解。
同时它带来了一系列的结论,即以 \(j+1,j+2,...,j+k\) 开始都不是最优解。
因为我们总能从 \(i+1,i+2,...,i+k\) 的位置开始找出一个串使得字典序比它小。
因此下一步 \(j=j+k+1\) ,因为比较两个相同的串没有意义,所以 \(j+=(i==j)\) 。
-
假如比到某一位置 \(s[i+k]>s[j+k]\) :
此时讨论同上,令 \(i=i+k+1,i+=(i==j)\) 即可。
当两个串比较长度 \(=n\) ,或某一头指针越过 \(n\) 了停止即可,答案就是那个还在 \(n\) 范围内的指针。
\(\\\)
Code
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 600010
#define R register
#define gc getchar
using namespace std;
int s[N];
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
int main(){
int n=rd(),i,j,k;
for(i=1;i<=n;++i) s[i]=s[n+i]=rd();
for(i=1,j=2;i<=n&&j<=n;){
for(k=0;k<=n&&s[i+k]==s[j+k];++k);
if(k==n) break;
if(s[i+k]>s[j+k]){i+=k+1;i+=(i==j);}
else{j+=k+1;j+=(i==j);}
}
int ans=min(i,j);
for(i=1;i<=n;++i) printf("%d ",s[i+ans-1]);
return 0;
}