2019 CCPC 网络选拔 Kth-occurrence

题意

给出一个字符串,每次询问其一个子串\([S_l,S_r]\)在原串中第\(k\)次出现所在的位置(开头位置)


解法

题意很简洁,思路也很简洁

就是代码巨难打

总之这道题还是让我很大程度上加深了对于\(SAM\)的认识啦,还去学了一下线段树合并

首先,根据后缀自动机的性质我们能知道题目所要求的的实际上是

\([S_l,S_r]\)所代表串所在后缀自动机的结点\(endpos\)集合中的第\(k\)个数

求区间第\(k\)大?权值线段树上啦

我们知道,对于后缀自动机上的某个结点,其\(endpos\)集合是它在\(parent\)树上所有儿子\(endpos\)集合的并

我们对于\(parent\)树上的每个节点,开一颗权值线段树,每个前缀初始化为其尾位置

那么对于我们想要求出某个结点的\(endpos\)集合,只要将其儿子的权值线段树合并到它上面来就行了

为了节省空间,这里每颗权值线段树都是动态开点的

那么我们如何查询一个子串\([S_l,S_r]\)在原串的后缀自动机上对应的节点呢?

我们可以采用树上倍增

先预处理所有前缀所对应的结点编号,建出\(parent\)树,预处理倍增数组\(f\)并进行一轮线段树合并求出所有节点的\(endpos\)集合

那么在查询\([S_l,S_r]\)时,我们先找到\([1,S_r]\)对应的结点,然后开始倍增跳

\([S_l,S_r]\)对应节点所代表的的最长串一定是\([x,S_r]\),我们需要找到一个最大的\(x\)使得\([S_l,S_r]\)是\([x,S_r]\)的后缀

倍增判断即可。其实我们完全可以把倍增理解为一种不断二分的过程,一次次的接近答案(因为在跳\(fa\)的过程中\(len\)是单调递减的)

找到对应的结点后在那个节点的权值线段树上查询第\(k\)大即可


代码

都封装了,打得巨好理解

#include <cstdio>
#include <cstring>

using namespace std;

const int N = 1e6 + 10;

int T, n, q;

char a[N];

struct segTree {

    int sz;
    int ls[N << 2], rs[N << 2], val[N << 2];

    void clear() {
        sz = 0;
    }

    void update(int cur) {
        val[cur] = val[ls[cur]] + val[rs[cur]];
    }

    int newnode() {
        ++sz;
        val[sz] = ls[sz] = rs[sz] = 0;
        return sz;
    }

    void mkchain(int& cur, int l, int r, int k) {
        if (!cur)   cur = newnode();
        if (l == r) return val[cur]++, void();
        int mid = l + r >> 1;
        if (k <= mid)
            mkchain(ls[cur], l, mid, k);
        else
            mkchain(rs[cur], mid + 1, r, k);
        update(cur);
    }

    int merge(int x1, int x2) {
        if (!x1 || !x2) return x1 + x2;
        int x = ++sz;
        ls[x] = merge(ls[x1], ls[x2]);
        rs[x] = merge(rs[x1], rs[x2]);
        val[x] = val[x1] + val[x2];
        return x;
    }

    int query(int cur, int l, int r, int k) {
        if (l == r)
            return l;
        int mid = l + r >> 1;
        if (k <= val[ls[cur]])
            return query(ls[cur], l, mid, k);
        else if (k <= val[cur])
            return query(rs[cur], mid + 1, r, k - val[ls[cur]]);
        else
            return -1;
    }

} tr;

struct SAM {

    int sz, lst;
    int len[N], fa[N], ch[N][30];

    int pos[N], dep[N], rt[N], f[N][30];

    int cap;
    int head[N], to[N << 1], nxt[N << 1];

    void clear() {
        sz = lst = 1, cap = 0;
        memset(head, 0, sizeof head);
        memset(len, 0, sizeof len);
        memset(fa, 0, sizeof fa);
        memset(ch, 0, sizeof ch);
        memset(rt, 0, sizeof rt);
    }

    void add(int x, int y) {
        to[++cap] = y, nxt[cap] = head[x], head[x] = cap;
    }

    void insert(int po, int c) {
        int cur = ++sz, p = lst;
        pos[po] = cur, len[cur] = po;
        for (; p && !ch[p][c]; p = fa[p])   ch[p][c] = cur;
        if (!p)
            fa[cur] = 1;
        else {
            int q = ch[p][c];
            if (len[q] == len[p] + 1)   fa[cur] = q;
            else {
                int nq = ++sz;
                fa[nq] = fa[q], len[nq] = len[p] + 1;
                for (; p && ch[p][c] == q; p = fa[p])   ch[p][c] = nq;
                memcpy(ch[nq], ch[q], sizeof ch[q]);
                fa[q] = fa[cur] = nq;
            }
        }
        lst = cur;
        tr.mkchain(rt[cur], 1, n, po);
    }

    void DFS(int cur) {
        for (int i = 1; i <= 20; ++i)   f[cur][i] = f[f[cur][i - 1]][i - 1];
            for (int i = head[cur]; i; i = nxt[i]) {
            f[to[i]][0] = cur;
            DFS(to[i]);
            rt[cur] = tr.merge(rt[cur], rt[to[i]]);
        }
    }

    void link() {
        for (int i = 2; i <= sz; ++i)   add(fa[i], i);
        DFS(1);
    }

    int solve(int l, int r, int k) {
        int cur = pos[r];
        for (int i = 20; i >= 0; --i) {
            int p = f[cur][i];
            if (l + len[p] - 1 >= r)    cur = p;
        }
        int ans = tr.query(rt[cur], 1, n, k);
        return (ans == -1) ? ans : ans - (r - l);
    }

} sam;

int main() {

    scanf("%d", &T);

    while (T--) {

        scanf("%d%d", &n, &q);
        scanf("%s", a + 1);

        sam.clear(), tr.clear();
        for (int i = 1; i <= n; ++i)    sam.insert(i, a[i] - 'a' + 1);      

        sam.link();

        int l, r, k;
        while (q--) {
            scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", sam.solve(l, r, k));
        }

    }

    return 0;
}

原文地址:https://www.cnblogs.com/VeniVidiVici/p/11449473.html

时间: 2024-11-10 03:27:08

2019 CCPC 网络选拔 Kth-occurrence的相关文章

2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分

题意:给你一个长度为n的字符串,每次询问给出三个数:L , R , K,表示原串 L 到 R 的子串在原串第K次出现的首字母的位置 解题思路:对子串的大量操作,不难想到后缀数组(后缀树/后缀自动机不会,所以没想到),注意到子串s[L.....R]必然是某一个后缀的前缀,所以所有前缀是该子串的后缀的排名(即rank数组的值)必定连续,也就是说在后缀数组(sa数组)中,下标是连续的,那么就是求区间第K大了(因为sa数组的值代表的是在字符串中的位置)(这里区间第K大我用划分树求),至于这一段区间的起点

2019 CCPC网络赛

一到网络赛,大家都是东亚之光 1001 00:23:46 solved by hl 签到 枚举一下位就行了 #include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <bitset

2019 CCPC - 网络选拔赛 D path(求第k短路)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6705 题目大意:给出n个点,m条边,q个询问,每个询问回答第$k_{i}$短路 解题报告:使用STL中的multiset,好处是头结点和尾结点都方便删除,记录每个以i结束的边的权值,然后再进行扩展,当mulitiset中的size大于询问中最大的k时,将尾部的点给删掉,还有个剪枝就是一开始建图的时候按权值降序建图,这样用链式前向星遍历的时候,边的顺序就是升序的,在bfs增广的时候可以剪枝,详细见代码

hdoj6703 2019 CCPC网络选拔赛 1002 array

题意 description You are given an array a1,a2,...,an(?i∈[1,n],1≤ai≤n). Initially, each element of the array is **unique**. Moreover, there are m instructions. Each instruction is in one of the following two formats: 1. (1,pos),indicating to change the

CCPC 网络选拔 array

题意 给一个\(1\)到\(n\)的排列 现在有\(m\)个操作,每个操作是下面的一种: \((1,pos)\),指把\(pos\)位上的数增加\(10,000,000\) \((2,r,k)\),询问操作,你需要输出一个数满足下列三个条件 这个数不等于\(a_i(1\leq i \leq r)\)中的任意一个 这个数不小于\(k\) 是满足上两个条件的数中的最小的一个 \(T\leq 10,1\leq n \leq 10^5, 1\leq m \leq 10^5 ,1\leq k\leq n\

hdu6153 A Secret CCPC网络赛 51nod 1277 KMP

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6153 题意: 给出两个字符串S1,S2,求S2的所有后缀在S1中出现的次数与其长度的乘积之和. 思路: CCPC网络赛题解: https://post.icpc-camp.org/d/714-ccpc-2017 http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1277   是一样的 将s1,s2翻转,转化为求前缀在s1中出

树形DP CCPC网络赛 HDU5834 Magic boy Bi Luo with his excited tree

1 // 树形DP CCPC网络赛 HDU5834 Magic boy Bi Luo with his excited tree 2 // 题意:n个点的树,每个节点有权值为正,只能用一次,每条边有负权,可以走多次,问从每个点出发的最大获益 3 // 思路: 4 // dp[i]: 从i点出发回到i点的最大值 5 // d[i][0] 从i点出发不回来的最大值 6 // d[i][1] 从i点出发取最大值的下一个点 7 // d[i][2] 从i点出发取次大值 8 // dfs1处理出这四个 9

2016 CCPC 网络赛 B 高斯消元 C 树形dp(待补) G 状压dp+容斥(待补) H 计算几何

2016 CCPC 网络赛 A - A water problem 水题,但读题有个坑,输入数字长度很大.. B - Zhu and 772002 题意:给出n个数(给出的每个数的质因子最大不超过2000),选出多个数相乘得b.问有多少种选法让b 为完全平方数. tags:高斯消元,求异或方程组解的个数.   好题 每个数先素数分解开.  对于2000以内的每个素数p[i],这n个数有奇数个p[i]则系数为1,偶数个则系数为0,最后n个数的p[i]系数异或和都要为0才会使得最后的积为完全平方数.

2018 CCPC网络赛

2018 CCPC网络赛 Buy and Resell 题目描述:有一种物品,在\(n\)个地点的价格为\(a_i\),现在一次经过这\(n\)个地点,在每个地点可以买一个这样的物品,也可以卖出一个物品,问最终赚的钱的最大值. solution 用两个堆来维护,一个堆维护已经找到卖家的,一个堆维护还没找到卖家的. 对于第\(i\)个地点,在已经找到卖家的堆里找出卖的钱的最小值,如果最小值小于\(a_i\),则将卖家换成\(i\),然后将原来的卖家放到没找到卖家的那里:如果最小值对于\(a_i\)