洛谷 P3975 / loj 2102 [TJOI2015] 弦论 题解【后缀自动机】【拓扑排序】

作者: wjyyy 分类: 后缀自动机,字符串,拓扑排序,解题报告 发布时间: 2019-03-17 08:51

点击量:35

后缀自动机入门。

题目描述

为了提高智商,ZJY 开始学习弦论。

这一天,她在《String theory》中看到了这样一道问题:对于一个给定的长度为 \(n\) 的字符串,求出它的第 \(k\) 小子串是什么。你能帮帮她吗?

输入输出格式

输入格式:

第一行是一个仅由小写英文字母构成的字符串 \(s\)。

第二行为两个整数 \(t\) 和 \(k\),\(t\) 为 \(0\) 则表示不同位置的相同子串算作一个。\(t\) 为 \(1\) 则表示不同位置的相同子串算作多个。\(k\) 的意义见题目描述。

输出格式:

输出数据仅有一行,该行有一个字符串,为第 \(k\) 小的子串。如果子串数目不足 \(k\) 个,则输出 \(-1\)。

输入输出样例

输入样例:

aabc
0 3

输出样例:

aab

数据范围与约定

对于 \(10\%\) 的数据,\(n\le 1000\)。

对于 \(50\%\) 的数据,\(t=0\)。

对于 \(100\%\) 的数据,\(n\le 5 \times 10^5,\ t<2,\ k\le 10^9\)。

题解:

SAM 学习笔记…先鸽一会。

这个题是找出第 \(k\) 大子串。因为在字符串中,第一关键字是字符,而不是长度,所以可以一个字符一个字符的比对。

那么我们先构建出 SAM,如果 \(t=1\),还需要跳 parent 树来计算一个串出现了多少次。记一个状态的贡献为 \(r\)。

然后还需要对 SAM 反向拓扑排序/记忆化搜索,得出走这条边可能到达多少种子串,也就是 DAG 后面每一条路径的 \(r\) 之和,记为 \(f\)。

然后从起始节点开始检测。每次遍历 a~z,如果发现那条边的 \(f\) 小于 \(k\),那么第 \(k\) 小的不在那边,此时让 \(k\) 减去 \(f\)。如果发现 \(f\) 大于 \(k\),则进入这条边。因为我们选了这条边,而后缀自动机的每一个状态都代表了一个子串,因此要把 \(f\) 减去新到点的 \(r\)。

如果此时发现 \(k\ge 0\),说明第 \(k\) 小的子串就在刚才那一步的状态里。否则继续在 SAM 上跑。

时间复杂度 \(O(n)\)。

Code:

#include<cstdio>
#include<cstring>
struct edge
{
    int n,nxt;
    edge(int n,int nxt)
    {
        this->n=n;
        this->nxt=nxt;
    }
    edge(){}
}e[2000100];
int head[1000100],ecnt=-1;
void add(int from,int to)
{
    e[++ecnt]=edge(to,head[from]);
    head[from]=ecnt;
}
char s[500100];
int ch[26][1000100],mx[1000100],par[1000100],r[1000100],pcnt;
void build()
{
    int p=pcnt=1;
    for(int i=1;s[i]!='\0';++i)
    {
        int w=s[i]-'a';
        int np=++pcnt;
        mx[np]=mx[p]+1;
        r[np]=1;
        while(p&&!ch[w][p])
        {
            ch[w][p]=np;
            p=par[p];
        }
        if(!p)
            par[np]=1;
        else
        {
            int q=ch[w][p];
            if(mx[q]==mx[p]+1)
                par[np]=q;
            else
            {
                int nq=++pcnt;
                mx[nq]=mx[p]+1;
                while(p&&ch[w][p]==q)
                {
                    ch[w][p]=nq;
                    p=par[p];
                }
                for(int j=0;j<26;++j)
                    ch[j][nq]=ch[j][q];
                par[nq]=par[q];
                par[q]=nq;
                par[np]=nq;
            }
        }
        p=np;
    }
}
void dfs(int x)
{
    for(int i=head[x];~i;i=e[i].nxt)
    {
        dfs(e[i].n);
        r[x]+=r[e[i].n];
    }
}
int d[1000100];
int q[1000100],l=0,R=0;
long long f[1000100];
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%s",s+1);
    int t,k;
    scanf("%d%d",&t,&k);
    build();
    for(int i=2;i<=pcnt;++i)
        add(par[i],i);
    if(t)
        dfs(1);
    else
        for(int i=1;i<=pcnt;++i)
            r[i]=1;
    memset(head,-1,sizeof(head));
    ecnt=-1;
    for(int i=1;i<=pcnt;++i)
    {
        int flag=0;
        for(int j=0;j<26;++j)
            if(ch[j][i])
            {
                flag=1;
                ++d[i];
                add(ch[j][i],i);
            }
        if(!flag)
            q[++R]=i;
    }
    while(l<R)
    {
        int x=q[++l];
        f[x]+=r[x];
        for(int i=head[x];~i;i=e[i].nxt)
        {
            f[e[i].n]+=f[x];

            --d[e[i].n];
            if(!d[e[i].n])
                q[++R]=e[i].n;
        }
    }
    t=1;
    while(k>0)
    {
        int flag=0;
        for(int i=0;i<26;++i)
        {
            if(f[ch[i][t]]<k)
                k-=f[ch[i][t]];
            else
            {
                flag=1;
                k-=r[ch[i][t]];
                printf("%c",'a'+i);
                t=ch[i][t];
                break;
            }
        }
        if(!flag)
            break;
    }
    if(k>0)
        puts("-1");
    return 0;
}

说点什么

avatar
  Subscribe  
提醒
/* */