bzoj 1031: [JSOI2007]字符加密Cipher

1031: [JSOI2007]字符加密Cipher

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 5473   Solved: 2261
[ Submit][ Status][ Discuss]

Description

  喜欢钻研问题的JS同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法
:把需要加密的信息排成一圈,显然,它们有很多种不同的读法。例如下图,可以读作:

 bzoj 1031: [JSOI2007]字符加密Cipher_第1张图片

JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0把它们按照字符串的大小排序:07JSOI 7JSOI0 I07JSO JSOI07
 OI07JS SOI07J读出最后一列字符:I0O7SJ,就是加密后的字符串(其实这个加密手段实在很容易破解,鉴于这是
突然想出来的,那就^^)。但是,如果想加密的字符串实在太长,你能写一个程序完成这个任务吗?

Input

  输入文件包含一行,欲加密的字符串。注意字符串的内容不一定是字母、数字,也可以是符号等。

Output

  输出一行,为加密后的字符串。

Sample Input

JSOI07

Sample Output

I0O7SJ

HINT

对于100%的数据字符串的长度不超过100000。

Source

[ Submit][ Status][ Discuss]



题解:后缀数组。

后缀:后缀是指从某个位置i开始到整个字符串末尾结束的一个特殊子串。字符串S的从i开关的后缀表示为Suffix(S,i),也就是Suffix(S,i)=S[i…len(S)]。

所谓后缀数组,主要就是sa,rank

sa[i]表示排名为i的后缀的起点下标

rank[i]表示以第i位为起点的后缀的优先级

所以可知两个互为逆运算,知道一个后就可以o(n)的时间求出另一个。

要了解后缀数组就有先了解计数排序 http://baike.baidu.com/view/1209480.htm



下面进入正题: 倍增法构造后缀数组

例如一个长为2^k的后缀,可以把它分成前2^k-1,和后2^k-1两部分,我们可以把这两部分当成第一关键字和第二关键字来排序。

bzoj 1031: [JSOI2007]字符加密Cipher_第2张图片

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200003
using namespace std;
char ch[200003];
int a[N],v[N],sa[2][N],rk[2][N];//sa[i]表示排名为i的后缀的起点下标,rk[i]表示第i位的后缀的优先级
//之所以有两维是因为2^k是由两段2^k-1推来的,所以两维分别表示的是两段 
int n,m,k;
void calcsa(int sa[N],int rank[N],int SA[N],int Rank[N])
{
     for(int i=1;i<=n;++i)v[rank[sa[i]]]=i;//v[i]表示排名为i的后缀,最靠后的位置在哪一位
     for(int i=n;i>=1;--i)
         if(sa[i]>k)
             SA[v[rank[sa[i]-k]]--]=sa[i]-k;
     for(int i=n-k+1;i<=n;++i)
         SA[v[rank[i]]--]=i;
     for(int i=1;i<=n;++i)
         Rank[SA[i]]=Rank[SA[i-1]]+(rank[SA[i-1]]!=rank[SA[i]]||rank[SA[i-1]+k]!=rank[SA[i]+k]);//双关键字排序
}
void work()
{
	int p=0,q=1;
	for (int i=1;i<=n;i++) v[a[i]]++;//统计每个字符出现了多少次 
	for (int i=1;i<=256;i++) v[i]+=v[i-1];//统计出前缀和即可知道每个字符在串中的排名 
	for (int i=1;i<=n;i++)
	 sa[p][v[a[i]]--]=i;//因为每个字符不一定只出现了一次,但是不同的后缀不能用同样的排名,所以每次--,有效的防止了重复,并且保证了后面长度短的后缀排名更靠前 
	for (int i=1;i<=n;i++)//rk 与sa 互为逆运算,知道另一个就可以O(n)求出另一个 
	 rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);//如果相邻两个相同,那么他们的优先级也是相同的 
	k=1;//字符串的初始长度,因为是双关键字计数排序,所以刚开始sa[q]为0,相当于只看第一个关键字 
	while (k<n)
	{
		calcsa(sa[p],rk[p],sa[q],rk[q]);
		p^=1; q^=1; k<<=1;
	}
	for (int i=1;i<=n;i++)
	 {
	 	printf("%d ",sa[p][i]);
	 } 
	printf("\n");
	for (int i=1;i<=n;i++)//sa[p]中存储的就是最终后缀数组的排名 
	{
		if (sa[p][i]<=n/2)
		 printf("%c",ch[sa[p][i]+n/2-1]);//这里是为什么呢?以样例为例展环为链后为JSOI07JSOI07 ,那么根据排名sa[p]最终会变成 0077IIJJOOSS,因为是读最后一列,所以是sa[p][i]+n/2-1,即当前点的前一位,因为是环嘛,划一划就明白了。 
	}
}
int main()
{
	scanf("%s",ch+1);
	int len=strlen(ch+1);
	for (int i=1;i<=len;i++)//展环为链 
	{
		a[i]=int(ch[i]);
		a[i+len]=a[i];
		ch[i+len]=ch[i];
	}
	n=len<<1;
	work();
} 



你可能感兴趣的:(bzoj 1031: [JSOI2007]字符加密Cipher)