[JSOI2007]字符加密Cipher
题意:
喜欢钻研问题的\(JS\)同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法:
把需要加密的信息排成一圈,显然,它们有很多种不同的读法。例如下图,可以读作:
JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0把它们按照字符串的大小排序:07JSOI 7JSOI0 I07JSO JSOI07
OI07JS SOI07J读出最后一列字符:I0O7SJ,就是加密后的字符串(其实这个加密手段实在很容易破解,鉴于这是
突然想出来的,那就^^)。但是,如果想加密的字符串实在太长,你能写一个程序完成这个任务吗?
思路:
后缀数组,断环成链。
设字符串\(s\)长度为\(len\),考虑将字符串\(s\)复制一份接在\(s\)的后面,则我们需要关注的是,后缀\(\in [1,len]\)的每个后缀中,长度为\(len\)的前缀。
假设这\(len\)个后缀分别标号为\(suf_1,suf_2,...,suf_i, i \in [1,len]\),同时假设这\(len\)个后缀对应的长度为\(len\)的前缀分别标号为\(pre_1,pre_2,..pre_i\),\(i \in [1,len]\),为了求得答案,我们需要将前缀\(pre_{1...len}\)从小到大排序。
现在会发现,后缀数组只能将后缀排序,也就是只能找到后缀\(suf_{1...len}\)的大小关系,而无法找到\(pre_{1...len}\)的大小关系。
那么怎么做呢?
仔细观察! 假设后缀\(suf_{1...len}\)已经从小到大排好序,则有
若\(suf_i<suf_j\),则会有出现两种情况
-
\(pre_i<pre_j\),因为前缀已经小于,则无论后面加什么都无法改变大小关系。
-
\(pre_i=pre_j\),此时,两前缀相等,可以发现在排好序的后缀中,会在若干个区间内出现等长且相等的前缀(结合后缀数组排好序后的后缀状态思考,其实本质上就是LCP)。
仔细想想我们要求的是什么,是不是\(pre_{1...len}\)的大小关系?
那么你会发现,对后缀\(suf\)排序好后,前缀\(pre\)也排好序了,此时两者是等价的。
所以对后缀\(suf_{1...len}\)排序,套一套后缀数组即可。
ps:
关于第\(2\)点加粗语句的理解,假设字符串为"aaaacd",则后缀有"aaaacd","aaacd","aacd","acd","cd","d"。
对其排序后,从小到大分别是"aaaacd","aaacd","aacd","acd","cd","d"。那么如果观察的前缀长度是\(2\),则\([1,3]\)区间中的长度为2的前缀都相等。
会发现其实本质上就是一个后缀数组中对\(LCP\)的理解,本题中的前缀长度就是前面假设的\(len\),其实全部都是围绕着这个前缀长度来讨论的,第一个点可以看作是在\(len\)前做决定来排序,第二点可以看作是在\(len\)后做决定来排序,此时排序后的这些相等的等长前缀可能会连续出现多次,具有连续性,这样理解也许会更好些。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010;
int wa[N],wb[N],wv[N],wss[N],rak[N],height[N],cal[N],n,sa[N];
char s[N];
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 *sa,int n,int M) {
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<M;i++) wss[i]=0;
for(i=0;i<n;i++) wss[x[i]=r[i]]++;
for(i=1;i<M;i++) wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,M=p) {
for(p=0,i=n-j;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<n;i++) wv[i]=x[y[i]];
for(i=0;i<M;i++) wss[i]=0;
for(i=0;i<n;i++) wss[wv[i]]++;
for(i=1;i<M;i++) wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return;
}
void calheight(int *r,int *sa,int n) {
int i,j,k=0;
for(i=1;i<=n;i++) rak[sa[i]]=i;
for(i=0;i<n;height[rak[i++]]=k)
for(k?k--:0,j=sa[rak[i]-1];r[i+k]==r[j+k];k++);
for(int i=n;i;i--)rak[i]=rak[i-1],sa[i]++;
}
int main(){
int cas=1;
while(~scanf("%s",s+1)){
n=strlen(s+1);
for(int i=n+1;i<=n+n;++i){
s[i]=s[i-n];
}
s[2*n+1]='\0';
for(int i=1;i<=n+n;i++)
cal[i]=s[i];
cal[2*n+1]=0;
da(cal+1,sa,2*n+1,200);
calheight(cal+1,sa,2*n);
for(int i=1;i<=2*n;++i){
if(sa[i]<=n){//从小到大排名 取小于等于n的后缀的末尾位
printf("%c",s[sa[i]+n-1]);
}
}
}
}