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

传送门

题意:
对给定字符串\(s\),求其第\(k\)小子串,重复串被计入以及不被计入这两种情况都需考虑。

思路:
首先构建后缀自动机,之后就考虑在后缀自动机上\(dp\)
我们知道如果要考虑重复串,那么就会与一个结点的\(endpos\)集合的大小有关,对于一条边\((u,v)\),如果结点\(u\)\(endpos\)集合大小为\(x\),那么就说明从\(u\)出发到达\(v\),会多出\(x\)种选择。
如果不考虑重复串,直接强制集合大小为\(1\)即可。
之后逆着拓扑序\(dp\)就行,求出从每个结点出发的子串个数。
最后求第\(k\)小的时候,有一些细节需要注意一下,比如从\(u\)\(v\)\(k\)应该减去\(|endpos(v)|\),因为现在的字符串会有\(|endpos(v)|\)个。
感觉后缀自动机好神奇...好多的性质以及用法...

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
struct node{
    int ch[26];
    int len, fa;
    node(){memset(ch, 0, sizeof(ch)), len = 0;}
}dian[N];
int last = 1, tot = 1;
ll f[N], sum[N];
int n, k, op;
char s[N];
int c[N], a[N];
void add(int c) {
    int p = last;
    int np = last = ++tot;
    dian[np].len = dian[p].len + 1;
    f[np] = 1;
    for(; p && !dian[p].ch[c]; p = dian[p].fa) dian[p].ch[c] = np;
    if(!p) dian[np].fa = 1;
    else {
        int q = dian[p].ch[c];
        if(dian[q].len == dian[p].len + 1) dian[np].fa = q;
        else {
            int nq = ++tot; dian[nq] = dian[q];
            dian[nq].len = dian[p].len + 1;
            dian[q].fa = dian[np].fa = nq;
            for(; p && dian[p].ch[c] == q; p = dian[p].fa) dian[p].ch[c] = nq;
        }
    }
}
void dfs(int u) {
    if(k <= 0) return;
    for(int i = 0; i < 26; i++) {
        int v = dian[u].ch[i];
        if(k > sum[v]) k -= sum[v];
        else {
            cout << char(i + 'a');
            k -= f[v];
            dfs(v);
            return;
        }
    }
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> s + 1 >> op >> k;
    int n = strlen(s + 1);
    for(int i = 1; i <= n; i++) add(s[i] - 'a');
    for(int i = 1; i <= tot; i++) c[dian[i].len]++;
    for(int i = 1; i <= tot; i++) c[i] += c[i - 1];
    for(int i = 1; i <= tot; i++) a[c[dian[i].len]--] = i;
    for(int i = tot; i; i--) {
        if(op) f[dian[a[i]].fa] += f[a[i]];
        else f[a[i]] = 1;
    }
    f[1] = 0;
//长度从大到小,逆着拓扑序
//每个结点的next指针指向的点长度肯定不小于它
    for(int i = tot; i >= 1; i--) {
        sum[a[i]] = f[a[i]];
        for(int j = 0; j < 26; j++) {
            int v = dian[a[i]].ch[j];
            if(v) sum[a[i]] += sum[v];
        }
    }
    if(sum[1] < k) cout << -1;
    else dfs(1);
    return 0;
}

转载于:https://www.cnblogs.com/heyuhhh/p/11438848.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值