首页 > 代码库 > 大视野 1031 字符加密Cipher(后缀数组)

大视野 1031 字符加密Cipher(后缀数组)

字符加密Cipher

Description

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

 

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

Input

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

Output

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

Sample Input

JSOI07

Sample Output

I0O7SJ


一开始用string数组排序做的,果断超内存了。

分析一下给到的串:

07JSOI——>07+……+I
7JSOI0——>7+……+0
I07JSO——>I07+……+O
JSOI07——>JSOI07+(7)
OI07JS——>OI07+……+S
SOI07J——>SOI07+J

可以发现07,7,I07,JSOI07,OI07,SOI07就是原字符串的所有后缀,所以在排序时,我们只需对这些后缀排序即可。这样就要用到后缀数组。

注意:由于是环状,因此需要先复制倍增字符串,得到新串后,给新串前半部分排个序,再依次输出各自在倍增后字符串中的末尾位置即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200010;
char str[N];
int *rank, sa[N];
int wa[N], wb[N], wm[N];
bool comp(int *r, int a, int b, int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}
void get_sa(char *r, int *sa, int n, int m)
{
    int *x = wa, *y = wb, *t, i, j, p;
    for(i = 0; i < m; ++i) wm[i] = 0;
    for(i = 0; i < n; ++i) wm[x[i] = r[i]]++;
    for(i = 1; i < m; ++i) wm[i] += wm[i-1];
    for(i = n-1; i >= 0; --i) sa[--wm[x[i]]] = i;
    for(i = 0, j = 1, p = 0; p < n; j <<= 1, 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 < m; ++i) wm[i] = 0;
        for(i = 0; i < n; ++i) wm[x[y[i]]]++;
        for(i = 1; i < m; ++i) wm[i] += wm[i-1];
        for(i = n-1; i >= 0; --i) sa[--wm[x[y[i]]]] = y[i];
        for(t = x, x = y, y = t, i = p = 1, x[sa[0]] = 0; i < n; ++i) {
            x[sa[i]] = comp(y, sa[i], sa[i-1], j) ? p-1 : p++;
        }
    }
    rank = x;
}

int main()
{
    while(gets(str) != NULL) {
        int len = strlen(str);
        for(int i = 0; i < len; i++)
            str[i + len] = str[i];
        str[2 * len] = 0;
        get_sa(str, sa, len * 2 + 1, 256);
        for(int i = 0; i <= 2 * len; i++)
            if(sa[i] < len)
                printf("%c", str[sa[i] + len - 1]);
        printf("\n");
    }
    return 0;
}



大视野 1031 字符加密Cipher(后缀数组)