Luogu P3975 [TJOI2015]弦论

本文介绍了一种使用后缀自动机解决子串问题的方法,包括重复子串和不重复子串的第k大子串问题。通过构建后缀自动机,对字符串的前缀等价类进行统计,利用类似后缀排序的方法和Trie树的路径数统计,求得每个等价类在字符串中出现的次数。

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

题目链接 \(Click\) \(Here\)

题目大意:

  • 重复子串不算的第\(k\)大子串
  • 重复子串计入的第\(k\)大子串
写法:后缀自动机。
\(OI\) \(Wiki\)上介绍的写法不太一样,因为要同时解决两个问题。
把字符串每个前缀所在等价类的\(siz\)记为\(1\),然后在\(parent\) \(tree\)上跑一次统计,就可以求出来每一个等价类在串中出现的次数。这里采用类似后缀排序的方法,对字符串按\(len\)为关键字进行排序。至于经过每个点的路径数\(sum\),可以在\(Trie\)边上对后面节点的\(sum\)(=每一个等价类在串中出现次数)求和得到(初始值等于\(siz\),因为每个点还有不选的情况)。
不要忘了把\(siz[1]\)\(sum[1]\)清空!
#include <bits/stdc++.h>
using namespace std;

const int N = 1100010;

char s[N];
int las = 1, node = 1;
int fa[N], len[N], siz[N], ch[N][26];

void extend (int c) {
    register int p, q, x, y;
    p = las, q = ++node; las = q;
    len[q] = len[p] + 1; siz[q] = 1;
    while (p != 0 && ch[p][c] == 0) {
        ch[p][c] = q;
        p = fa[p];
    }
    if (p == 0) {
        fa[q] = 1;
    } else {
        x = ch[p][c];
        if (len[x] == len[p] + 1) {
            fa[q] = x;
        } else {
            y = ++node;
            fa[y] = fa[x];
            fa[x] = fa[q] = y;
            len[y] = len[p] + 1;
            memcpy (ch[y], ch[x], sizeof (ch[x]));
            while (p != 0 && ch[p][c] == x) {
                ch[p][c] = y;
                p = fa[p];
            }
        }
    }
}

int t, k, nd[N], bin[N]; long long sum[N];

void output (int u) {
    if (k <= siz[u]) return;
    k -= siz[u];
    register int i;
    for (i = 0; i < 26; ++i) {
        if (ch[u][i]) {
            if (k > sum[ch[u][i]]) {
                k -= sum[ch[u][i]];
            } else {
                printf ("%c", i + 'a');
                output (ch[u][i]);
                return;
            }
        }
    }
}

int main () {
    scanf ("%s %d %d", s, &t, &k);
    register int i, j, n;
    n = strlen (s);
    for (i = 0; i < n; ++i) extend (s[i] - 'a');
    for (i = 1; i <= node; ++i) bin[len[i]]++;
    for (i = 1; i <= node; ++i) bin[i] += bin[i - 1];
    for (i = 1; i <= node; ++i) nd[bin[len[i]]--] = i;
    for (i = node; i >= 1; --i) siz[fa[nd[i]]] += siz[nd[i]];
    for (i = 1; i <= node; ++i) t == 0 ? (sum[i] = siz[i] = 1) : (sum[i] = siz[i]);
    siz[1] = sum[1] = 0;
    for (i = node; i >= 1; --i) {
        for (j = 0; j < 26; ++j) {
            if (ch[nd[i]][j]) {
                sum[nd[i]] += sum[ch[nd[i]][j]];
            }
        }
    }
    if (sum[1] < k) {
        puts ("-1");
    } else {
        output (1);
    }
}

\(p.s\)关于后缀数组求第一问的方法:

枚举每一个后缀,第i个后缀对答案的贡献为\(len−sa[i]+1−height[i]\)

转载于:https://www.cnblogs.com/maomao9173/p/10452644.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值