[ 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;
}
posted @ 2018-11-20 15:29  SGCollin  阅读(143)  评论(0编辑  收藏  举报