zoukankan      html  css  js  c++  java
  • [BZOJ]1031 字符加密Cipher(JSOI2007)

      持续划水中……

      感觉BZOJ上AC人数多的基本都是一些模板题,也就是某些算法的裸题。这些题目mark一下到时候回来复习也是不错的选择。

    Description

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

       

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

    Input

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

    Output

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

    Sample Input

      JSOI07

    Sample Output

      I0O7SJ

    HINT

      字符串的长度不超过100000。

     

    Solution

      后缀数组裸题嘛,小C觉得没什么可说的。

      本质就是一个长度为2n字符串有n个长度为n的子串,将它们排序。

      不过在这里小C要好好讲一讲自己对于后缀数组O(nlogn)算法的理解。

      虽然一开始小C看这个算法时也是头皮发麻,但是仔细想想代码中的道理也是非常好理解的。

      后缀数组核心代码只有9行,其中4行预处理,1行倍增,4行正式处理,而且预处理部分和正式处理部分本质是相同的。

      具体参见小C代码后面的注释。

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #define MN 200005
    #define MS 256
    using namespace std;
    int mp[MN],rk[2][MN],sa[2][MN];
    char c[MN];
    int n,bn,K,g;
    
    inline int read()
    {
        int n=0,f=1; char c=getchar();
        while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
        while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
        return n*f;
    }
    
    void work(int* SA,int* RK,int* sa,int *rk)
    {
        register int i;
        //正式处理部分:前3行以rk为第一关键字求出SA,后一行无脑求RK。(注意顺序) 
        for (i=1;i<=n;++i) mp[rk[sa[i]]]=i;  //不用前缀和,直接根据串x在sa中的排名得到mp[rk[x]]的前缀和。 
        for (i=n;i;--i) if (sa[i]>K) SA[mp[rk[sa[i]-K]]--]=sa[i]-K;
                                //对于扩展后长度大于K的后缀x,利用后缀x+K在sa中的排名得到x在SA中的排名。 
        for (i=n-K+1;i<=n;++i) SA[mp[rk[i]]--]=i;
                        //然后就只剩下扩展后长度小等于K的后缀x,排名一定比大于K的后缀小,所以后处理。 
        for (i=1;i<=n;++i) RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]] || rk[SA[i]+K]!=rk[SA[i-1]+K]);
                                                                          //无脑求RK,注意用到sa。
        //为了执行效率的提高,通常在对后缀排序时,如果发现某次扩展后,所有后缀的rk都不相同,即可停止倍增。 
    }
    
    void prework()
    {
        register int i;
        //预处理部分:前3行用基数排序求出sa[0],后1行无脑求rk[0]。 
        for (i=1;i<=n;++i) ++mp[c[i]];
        for (i=1;i<MS;++i) mp[i]+=mp[i-1];
        for (i=1;i<=n;++i) sa[0][mp[c[i]]--]=i;
        for (i=1;i<=n;++i) rk[0][sa[0][i]]=rk[0][sa[0][i-1]]+(c[sa[0][i]]!=c[sa[0][i-1]]);
                                    //注意求rk的时候要按照sa的顺序。 
        //倍增部分:g代表数组滚动,K是扩展前的大小,扩展一次后sa内的后缀长度变为K*2。 
        for (g=0,K=1;K<=bn;K<<=1,g^=1) work(sa[g^1],rk[g^1],sa[g],rk[g]);
        //至于求height数组的话,只要记住height[rk[i]]>=height[rk[i-1]]这个性质就行了。 
    }
    
    int main()
    {
        register int i;
        scanf("%s",c+1); n=strlen(c+1);
        for (i=1;i<=n;++i) c[n+i]=c[i];
        bn=n; n<<=1;
        prework(); c[0]=c[n];
        for (i=1;i<=n;++i) if (sa[g][i]<=bn) putchar(c[sa[g][i]-1]);
    }

    Last Word

      本来后缀数组是不亚于LCT困扰小C的存在,现在发现理解了就不需要那样死记硬背了。

      最后吐槽一下BZOJ上各种病句连篇的题面,在Blog上贴这种东西尴尬癌都要犯了。

  • 相关阅读:
    Tomcatd断点调试Debug
    idea怎么部署Servlet
    ECMAScript基本语法——①与HTML的结合方式
    JavaScript简介
    程序员找工作,应该怎么应对面试官?
    你所未知的3种 Node.js 代码优化方式
    对 APM 用户的一次真实调查分析(上)
    Datadog Agent是啥?它消耗什么资源?
    Python 全栈开发 -- 开发环境篇
    成为运维界的「福尔摩斯」,你还需要3个帮手!
  • 原文地址:https://www.cnblogs.com/ACMLCZH/p/7104916.html
Copyright © 2011-2022 走看看