• 【BZOJ3998】弦论(TJOI2015)-后缀自动机


    测试地址:弦论
    做法:本题需要用到后缀自动机。
    先说点题外话:今天是一个特殊的日子,那就是本蒟蒻在BZOJ成功AC100道题啦!可喜可贺,可喜可贺……
    好了好了,话说回来,本题要求两种东西:第K大的子串和第K大本质不同的子串。一看这个数据范围,就知道O(nlogn)的后缀数组肯定非常拙计(当然如果你会O(n)构造就当我没说……),又根据后缀自动机的性质,它从起点开始的每条不同的路径都对应本质不同的子串,那么我们就对字符串建后缀自动机。接着我们分情况讨论:
    求第K大本质不同的子串。这个我们只要DFS一遍,求出从每一个点出发的子串数,然后再按照这个在后缀自动机上走即可。
    求第K大子串。这个比上面要复杂些,因为要求每种子串出现的次数。观察发现,字符串的每一个前缀都对它所有后缀做出1的贡献,而根据后缀自动机中后缀链接的定义,实际上一个前缀会对终点在它到根在后缀链接上的路径上的所有的子串做出1的贡献,那么如果我们知道哪些点是前缀节点,我们就可以一遍DFS求出每种子串出现的数目。哪些点是前缀节点呢?实际上,只要不是在建后缀自动机的时候“克隆”某个点后出现的点,都是前缀节点。那么我们求出每种子串出现次数后,按照第一种情况做即可。
    上面的所有步骤时间复杂度都是O(n)的,是很优秀的复杂度。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,tot=0,last,pre[1000010],ch[1000010][26],len[1000010];
    int T,k,first[1000010]={0},tote=0;
    int q[1000010],h,t;
    ll cnt[1000010],f[1000010]={0};
    char s[500010];
    bool vis[1000010]={0},cln[1000010]={0};
    struct edge
    {
        int v,next;
    }e[1000010];
    
    void init()
    {
        scanf("%s",s);
        scanf("%d%d",&T,&k);
    }
    
    void extend(char c)
    {
        int p,q,np,nq;
        np=++tot;
        len[np]=len[last]+1;
        p=last;
        while(p&&!ch[p][c-'a']) ch[p][c-'a']=np,p=pre[p];
        if (!p) pre[np]=1;
        else
        {
            q=ch[p][c-'a'];
            if (len[p]+1==len[q]) pre[np]=q;
            else
            {
                nq=++tot;
                cln[nq]=1;
                len[nq]=len[p]+1;
                pre[nq]=pre[q];
                for(int i=0;i<26;i++)
                    ch[nq][i]=ch[q][i];
                while(p&&ch[p][c-'a']==q) ch[p][c-'a']=nq,p=pre[p];
                pre[q]=pre[np]=nq;
            }
        }
        last=np;
    }
    
    void insert(int a,int b)
    {
        e[++tote].v=b;
        e[tote].next=first[a];
        first[a]=tote;
    }
    
    void build()
    {
        n=strlen(s);
        last=++tot;
        pre[last]=len[last]=0;
        for(int i=0;i<26;i++)
            ch[last][i]=0;
        for(int i=0;i<n;i++)
            extend(s[i]);
        for(int i=2;i<=tot;i++)
            insert(pre[i],i);
    }
    
    void dfs1(int v)
    {
        vis[v]=1;
        cnt[v]=cln[v]?0:1;
        for(int i=first[v];i;i=e[i].next)
        {
            if (!vis[e[i].v]) dfs1(e[i].v);
            cnt[v]+=cnt[e[i].v];
        }
        if (T==0) cnt[v]=1;
        if (v==1) cnt[v]=0;
    }
    
    void dfs2(int v)
    {
        vis[v]=1;
        f[v]=cnt[v];
        for(int i=0;i<26;i++)
            if (ch[v][i])
            {
                if (!vis[ch[v][i]]) dfs2(ch[v][i]);
                f[v]+=f[ch[v][i]];
            }
    }
    
    void findans(int v,ll k)
    {
        if (k>f[v]) {printf("-1");return;}
        k-=cnt[v];
        if (k<=0) return;
        for(int i=0;i<26;i++)
            if (ch[v][i])
            {
                if (f[ch[v][i]]<k) k-=f[ch[v][i]];
                else
                {
                    printf("%c",i+'a');
                    findans(ch[v][i],k);
                    break;
                }
            }
    }
    
    int main()
    {
        init();
        build();
        dfs1(1);
        memset(vis,0,sizeof(vis));
        dfs2(1);
        findans(1,k);
    
        return 0;
    }
  • 相关阅读:
    《metasploit渗透测试魔鬼训练营》靶机演练之第五章实战案例KingView 6.53版本CVE-2011-0406漏洞
    《metasploit渗透测试魔鬼训练营》学习笔记第五章--网络服务渗透攻击
    《metasploit渗透测试魔鬼训练营》靶机演练之第五章实战案例Oracle数据库
    《metasploit渗透测试魔鬼训练营》学习笔记第四章—web应用渗透
    《metasploit渗透测试魔鬼训练营》学习笔记第三章----情报搜集
    Kali-linux Arpspoof工具
    Kali-linux攻击路由器
    Kali-linux在树莓派上破解无线网络
    Kali-linux使用Easy-Creds工具攻击无线网络
    20155225 2016-2017-2 《Java程序设计》第五周学习总结
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793491.html
Copyright © 2020-2023  润新知