[BZOJ3998][TJOI2015]弦论(后缀自动机)

本文介绍如何利用后缀自动机解决字符串子串查找问题,并通过深度优先搜索(DFS)进行有效遍历。文中详细解释了预处理方法,包括求解有向无环图(DAG)中节点后的不同路径数,以及实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

我是超链接

题解:

因为后缀自动机可以识别串S的所有子串,所以它是解决这类问题十分方便的一个方法。我们可以顺着自动机这个有向无环图来DFS,对于每个节点预处理它往后还能经过多少不同的子串,再决定走哪条路就可以了:如果他的儿子的size>=k就说明第k小的子串的结尾在该子树中,否则k-size,然后从下个继续。有点主席树查询区间k大的意思。

这个预处理就相当于求DAG中一个节点往后还有多少不同的路径,是一个简单的DP,每次枚举当前节点的所有儿子它们累加过来就可以了。如果是相同子串不重复计算,每个点的初始权值就是1;否则每个点的初始权值就是它Right集合的大小。这种时候要特别注意输出的时候减去的也是Right集合的大小而不是1

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=1000005;
int p,q,np,nq,cnt,last,c[N],a[N],fa[N],step[N],ch[N*2][30],size[N],l,r[N];
char st[N];
void insert(int c)
{
    p=last; last=np=++cnt;
    step[np]=step[p]+1;
    while (p && !ch[p][c]) ch[p][c]=np,p=fa[p];
    if (!p) {fa[np]=1;return;}
    q=ch[p][c];
    if (step[q]==step[p]+1){fa[np]=q; return;}
    nq=++cnt; step[nq]=step[p]+1;
    memcpy(ch[nq],ch[q],sizeof(ch[q]));
    fa[nq]=fa[q]; fa[q]=fa[np]=nq;
    while (ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
void calc0()
{
    for (int i=cnt;i;i--)
    {
        int t=a[i];
        if (t!=1) size[t]++;
        for (int j=0;j<26;j++) size[t]+=size[ch[t][j]];
    }
}
void calc1()
{
    p=1;
    for (int i=0;i<l;i++)
    {
        p=ch[p][st[i]-'a'];
        r[p]++;                                                  
    } 
    for (int i=cnt;i;i--)
    {
        int t=a[i];
        r[fa[t]]+=r[t];
    }
    for (int i=cnt;i;i--)
    {
        int t=a[i];
        if (t!=1) size[t]=r[t];
        for (int j=0;j<26;j++) size[t]+=size[ch[t][j]];
    }
}
void print(int t,int k)
{
    p=1;
    while (k)
    {
        for (int i=0;i<26;i++)
          if (size[ch[p][i]]>=k)
          {
            printf("%c",i+'a'); p=ch[p][i];
            if (t==0) k--;else k-=r[p];
            break;
          }
          else k-=size[ch[p][i]];
    }
}
int main()
{
    scanf("%s",st);
    last=cnt=1; l=strlen(st);
    for (int i=0;i<l;i++) insert(st[i]-'a');
    int t,k;scanf("%d%d",&t,&k);
    for (int i=1;i<=cnt;i++) c[step[i]]++;
    for (int i=1;i<=cnt;i++) c[i]+=c[i-1];
    for (int i=1;i<=cnt;i++) a[c[step[i]]--]=i;
    if (t==0) calc0();
    else calc1();
    if (size[1]<k) printf("-1");
    else print(t,k);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值