• ●BZOJ 4310 跳蚤


    ●赘述题目

    给出一个字符串,要求分成k个子串,然后求出每个子串的字典序最大的子串(我称它为子子串),要使这k个子子串中的字典序最大的那个串(即魔力串)最小。输出该魔力串。

    (本题个人感觉很好,比较综合。属于后缀数组中等题。)

    ●题解

    方法:后缀数组+RMQ+二分

    既然是要“最大的最小”,那很“习惯”地就会想到二分,大体如下

    l = 1;
    r = n;  //所有的不同的子串的个数
    while ( l<=r ) {
       mid =( l + r ) / 2;
       if ( check ( mid ) )  //如果将第mid大的子串作为魔力串,检查是否合法
            ans = mid ,   r =mid - 1; //如果合法,记录答案
       else l = mid + 1;
    }

    那么,就面临着几个问题:

    1.上述代码中 n 的值多少?(即如何求不同的子串的个数):

        “一个串中不同子串的总数=∑(len-height[i]-sa[i])”,用这个式子就可以解决,至于为什么,自己去模拟一下吧。

    2.如何找出第mid大的子串? 我相信如果弄懂了上面那个式子,应该会有想法的。

    3.如何check(mid)?

         给出一种思路:反向遍历原串,用一个start和end分别记录当前的开头和结尾,

         若start到end这段子串的字典序大于了第mid大的子串,那么就在start和start-1之间“砍一刀”,并更新start和end,以及累计分成的段数。

         若start到end这段子串的字典序小于第mid大的子串,便只更新start的值

         最后,看累计的段数与输入k的大小关系,并判断返回true or false

    4.在3中,如何快速比较start到end这段子串与第mid大的子串的大小关系(当然不能逐位比较):

        用构建的height数组,建立ST表,用于RMQ快速求出两子串的LCP,然后就自己脑补吧。)

    把以上的步骤写成函数,也就差不多完成了。

    值得注意的是,对于该题的数据范围,我们求出的不同的子串的个数n应该会爆int的。无辜的我被这个错误折磨了整整5天。。。。。。

    /**************************************************************
        Problem: 4310
        User: 
        Language: C++
        Result: Accepted
        Time:2640 ms
        Memory:11656 kb
    ****************************************************************/
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #define ll long long
    using namespace std;
    char s[100006],k[100006];
    int t1[100006],t2[100006],sa[100006],ra[100006],he[100006],c[100005];
    int f[100006][20];
    int ls,n,ke,ks,aks=-1,ake=-1;
    ll l=1,r,mid;
    void build_array(int m)
    {
        int *x=t1,*y=t2,p;
        for(int i=0;i<m;i++) c[i]=0;
        for(int i=0;i<ls;i++) c[x[i]=s[i]-'!'+1]++;
        for(int i=1;i<m;i++) c[i]+=c[i-1];
        for(int i=ls-1;i>=0;i--) sa[--c[x[i]]]=i;
          
        for(int k=1;k<=ls;k<<=1)
        {
            p=0;
            for(int i=ls-k;i<ls;i++) y[p++]=i;
            for(int i=0;i<ls;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
              
            for(int i=0;i<m;i++) c[i]=0;
            for(int i=0;i<ls;i++) c[x[y[i]]]++;
            for(int i=1;i<m;i++) c[i]+=c[i-1];
            for(int i=ls-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
              
            swap(x,y);
            m=2; x[sa[0]]=1;
            for(int i=1;i<ls;i++)
            x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])?m-1:m++;
            if(m>ls) break;
        }
        for(int i=0;i<ls;i++) ra[sa[i]]=i; p=0;
        for(int i=0;i<ls;i++)
        {
            if(p) p--;
            if(ra[i]==0) continue;
            int j=sa[ra[i]-1];
            while(s[i+p]==s[j+p]) p++;
            he[ra[i]]=p;
        }
    }
    void get_Kth(ll x)
    {
        int ng;
        for(int i=0;i<ls;i++)
        {
            ng=ls-he[i]-sa[i];
            if(ng<x) x-=ng;
            else
            {
                ng=0;
            /*  for(int j=sa[i];j<=sa[i]+he[i]+x-1;j++)
                {
                    k[ng++]=s[j];
                }*/
                ks=sa[i]; ke=sa[i]+he[i]+x-1;
                /*k[ng]=0;*/
                break;
            }
        }
    }
    void make_ST()
    {
        for(int i=0;i<ls;i++) f[i][0]=he[i];
        for(int j=1;(1<<j)<ls;j++)
            for(int i=(1<<j)&&i<ls;i<ls;i++)
                 f[i][j]=min(f[i][j-1],f[i-(1<<(j-1))][j-1]);
    } 
    int rmq(int x,int y)
    {
        if(x>y) swap(x,y);
        if(x==y) return ls-sa[x];
        if(y-1==x) return f[y][0];
        int j=log(y-x-1)/log(2);
        return min(f[y][j],f[x+(1<<j)][j]);
    }
    bool check()
    {
        int x=ra[ks],y,end=ls-1,k=0;
        for(int i=ls-1;i>=0;i--)
        {
            y=ra[i];
            int o=rmq(x,y);
            o=min(o,min(ke-ks+1,end-i+1));
            if(ks+o-1>=ke&&i+o-1>=end) continue;
            if(ks+o-1<ke&&i+o-1>=end) continue;
            if(ks+o-1<ke&&i+o-1<end&&s[ks+o]>s[i+o]) continue;
            k++;
            end=i;
            if(s[end]>s[ks]) return 0;
        }
        return k+1<=n;
    }
    int main()
    {
        scanf("%d",&n);
        l=1;
        scanf("%s",s);
        ls=strlen(s);
        build_array(300);
        make_ST();
        for(int i=0;i<ls;i++) r+=ls-he[i]-sa[i];
        while(l<=r)
        {
            mid=(l+r)/2;
            get_Kth(mid);
            //puts(k);  
            if(check()) aks=ks,ake=ke,r=mid-1;
            else l=mid+1;
        }
        if(!(n>0)) get_Kth(mid),aks=ks,ake=ke;
        for(int i=aks;i<=ake;i++) putchar(s[i]); 
        puts("");
        return 0;
    }

    但程序的效率和排行上的差距不小,希望有好心人能告诉我优化的方法。

  • 相关阅读:
    ubuntu全版本通用换源教程
    【Pybind11】Python调用C++接口(Ubuntu下编译OpenCV)
    ubuntu18.04如何安装python3.5及其pip安装
    smb和rdp暴破差异分析
    如何修改smb服务的默认445端口?——官方回答是无法修改,但是可以使用端口转发
    开集识别——流形
    10种常见的进程注入技术的总结
    webmine和cryptowebminer挖矿——一句<iframe width=0 height=0 frameborder=0 src='https://webmine.cz/worker?key=[YOUR_API_KEY]'></iframe>搞定
    进程隐藏与进程保护(SSDT Hook 实现)(一)
    deepMiner —— 本质上类似coinhive,也是后端开启nodejs连接矿池,默认的连接门罗币矿池
  • 原文地址:https://www.cnblogs.com/zj75211/p/7149712.html
Copyright © 2020-2023  润新知