[JSOI2007]字符加密Cipher

题意:

喜欢钻研问题的\(JS\)同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法:
把需要加密的信息排成一圈,显然,它们有很多种不同的读法。例如下图,可以读作:
picture
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\),则会有出现两种情况

  1. \(pre_i<pre_j\),因为前缀已经小于,则无论后面加什么都无法改变大小关系。

  2. \(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]);
		}
	}
    }
}
posted @ 2021-03-25 15:20  Qquun  阅读(102)  评论(0)    收藏  举报