2019CCPC网络预选赛 1003 K-th occurrence 后缀自动机 + 二分 + 主席树

题意:给你一个长度为n的字符串,有m次询问,每次询问l到r的子串在原串中第k次出现的位置,如果没有输出-1。n, m均为1e5级别。

思路:后悔没学后缀数组QAQ,其实只要学过后缀数组这个题还是比较好想的。这个问题可以转化为有多少个后缀和后缀l的lcp长度大于等于r - l + 1。我们知道,在后缀数组中,两个后缀i, j的lcp是min(height[rank[j] + 1], height[rank[j] + 2], ....height[rank[i]])。那么,我们可以二分出一个最靠左的位置(假设这个位置是p),这个位置到rank[l]的height都是 >= r - l + 1的,即从p到rank[l]这些位置的后缀与l的lcp长度都是大于等于r - l + 1的。rank[l]的右边同理可得。那么,我们就可以知道有哪些后缀可能是答案了。那么还有一个问题,怎么知道它们中第k个位置呢?这个就是一个静态区间第k大问题,我们把sa[i]按顺序插入到主席树中,然后再二分出的两个端点之间询问第k大的位置即可。

代码(后缀数组板子copy网上的QAQ):

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
char s[maxn];
int n, tot;
int root[maxn];
struct node {
    int sum;
    int lc, rc;
};
node tr[maxn * 50];
struct SA {
    int sa[maxn], x[maxn], y[maxn], c[maxn];
    int rank[maxn], height[maxn], h[maxn];
    int f[maxn][18];

    void build_sa(int m) {
        for (int i = 0; i <= m; i++) c[i] = 0;
        for (int i=1; i<=n; ++i) ++c[x[i]=s[i]];
    //c数组是桶
    //x[i]是第i个元素的第一关键字
        for (int i=2; i<=m; ++i) c[i]+=c[i-1];
    //做c的前缀和,我们就可以得出每个关键字最多是在第几名
        for (int i=n; i>=1; --i) sa[c[x[i]]--]=i;
        for (int k=1; k<=n; k<<=1) {
            int num=0;
            for (int i=n-k+1; i<=n; ++i) y[++num]=i;
    //y[i]表示第二关键字排名为i的数,第一关键字的位置
    //第n-k+1到第n位是没有第二关键字的 所以排名在最前面
            for (int i=1; i<=n; ++i) if (sa[i]>k) y[++num]=sa[i]-k;
    //排名为i的数 在数组中是否在第k位以后
    //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
    //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
            for (int i=1; i<=m; ++i) c[i]=0;
    //初始化c桶
            for (int i=1; i<=n; ++i) ++c[x[i]];
    //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了
            for (int i=2; i<=m; ++i) c[i]+=c[i-1]; //第一关键字排名为1~i的数有多少个
            for (int i=n; i>=1; --i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
    //因为y的顺序是按照第二关键字的顺序来排的
    //第二关键字靠后的,在同一个第一关键字桶中排名越靠后
    //基数排序
            swap(x,y);
    //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思
            x[sa[1]]=1;
            num=1;
            for (int i=2; i<=n; ++i)
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
    //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
            if (num==n) break;
            m=num;
    //这里就不用那个122了,因为都有新的编号了
        }
    }

    void get_height() {
        int k=0;
        for (int i=1; i<=n; ++i) rank[sa[i]]=i;
        for (int i=1; i<=n; ++i) {
            if (rank[i]==1) continue;//第一名height为0
            if (k) --k;//h[i]>=h[i-1]-1;
            int j=sa[rank[i]-1];
            while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k;
            height[rank[i]]=k;//h[i]=height[rk[i]];
        }
        for (int i = 1; i <= n; i++) h[i] = height[rank[i]];
    }

    void build_st() {
        for (int i = 1; i <= n; i++)
            f[i][0] = height[i];
        int t = log(n) / log(2) + 1;
        for (int j = 1; j < t; j++) {
            for (int i = 1; i <= n - (1 << j) + 1; i++)
                f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }

    int query(int l, int r) {
        if(l > r) return 0;
        int k = log(r - l + 1) / log(2);
        return min(f[l][k], f[r - (1 << k) + 1][k]);
    }
};

SA solve;

int build(int l, int r) {
    int p = ++tot;
    if (l == r) {
        tr[p].sum = 0;
        tr[p].lc = tr[p].rc = 0;
        return p;
    }
    int mid = (l + r) >> 1;
    tr[p].lc = build(l, mid);
    tr[p].rc = build(mid + 1, r);
    tr[p].sum = tr[tr[p].lc].sum + tr[tr[p].rc].sum;
    return p;
}
int insert(int now, int l, int r, int x, int val) {
    int p = ++tot;
    tr[p] = tr[now];
    if(l == r) {
        tr[p].sum = 1;
        return p;
    }
    int mid = (l + r) >> 1;
    if(x <= mid) tr[p].lc = insert(tr[now].lc, l, mid, x, val);
    else tr[p].rc = insert(tr[now].rc, mid + 1, r, x, val);
    tr[p].sum = tr[tr[p].lc].sum + tr[tr[p].rc].sum;
    return p;
}
int query(int lnow, int rnow, int l, int r, int remain) {
    if(l > r) return 0;
    if(l == r) {
        return l;
    }
    int mid = (l + r) >> 1;
    int tmp = tr[tr[rnow].lc].sum - tr[tr[lnow].lc].sum;
    if(tmp >= remain) return query(tr[lnow].lc, tr[rnow].lc, l, mid, remain);
    else return query(tr[lnow].rc, tr[rnow].rc, mid + 1, r, remain - tmp);
}
int main() {
    int T, m, l, r, k, ql, qr;
//    freopen("cin.txt", "r", stdin);
//    freopen("cout.txt", "w", stdout);
    scanf("%d", &T);
    while(T--) {
        tot = 0;
        scanf("%d%d", &n, &m);
        scanf("%s", s + 1);
        for (int i = 1; i <= n; i++)
            s[i] -= (‘a‘ - 1);
        solve.build_sa(30);
        solve.get_height();
        solve.build_st();
        root[0] = build(1, n);
        for (int i = 1; i <= n; i++) {
            root[i] = insert(root[i - 1], 1, n, solve.sa[i], 1);
        }
        for (int i = 1; i <= m; i++) {
            scanf("%d%d%d", &l, &r, &k);
            int p = solve.rank[l];
            int L = 1, R = p;
            while(L < R) {
                int mid = (L + R) >> 1;
                if(solve.query(mid + 1, p) < r - l + 1) L = mid + 1;
                else R = mid;
            }
            ql = L;
            L = p, R = n;
            while(L < R) {
                int mid = (L + R + 1) >> 1;
                if(solve.query(p + 1, mid) < r - l + 1) R = mid - 1;
                else L = mid;
            }
            qr = R;
            if(qr - ql + 1 < k) printf("-1\n");
            else printf("%d\n", query(root[ql - 1], root[qr], 1, n, k));
        }
    }

}

  

原文地址:https://www.cnblogs.com/pkgunboat/p/11407332.html

时间: 2024-11-15 21:32:11

2019CCPC网络预选赛 1003 K-th occurrence 后缀自动机 + 二分 + 主席树的相关文章

hdu6704 2019CCPC网络选拔赛1003 K-th occurrence 后缀数组

题意:给你一个长度为n的字符串,有q个询问,每次询问一个子串s(l,r)第k次出现的位置,若子串出现次数少于k次输出-1. 解题思路:先把SA跑出来,然后对于每次询问可以由l和rank[]找到l在所有后缀中的排名,再用两次二分求出使得LCP(L,R)包含s(l,r)的最大区间[L,R],LCP可以借助height[]的性质和ST表求得,即[L,R]包含rank[l]且min{height[L+1],height[L+2],...,height[R]}>=r-l+1.现在问题就转化为了求[L,R]

2019年华南理工大学程序设计竞赛(春季赛) K Parco_Love_String(后缀自动机)找两个串的相同字串有多少

https://ac.nowcoder.com/acm/contest/625/K 题意: 给出Q 个询问 i , 求 s[0..i-1] 与 s[i...len-1] 有多少相同的字串 分析: 给出了查询 , 容易想到先预处理出答案好吧 , 字符串的问题也容易想到后缀自动机 ,但是我们该怎么使用呢? 下面提供我的思路: 我们建立出SAM后 , 跑一边拓扑排序 ,根据SAM跑出来的拓扑排序的序列特性 , 我们可以求出 在当前状态st 的最大串字符出现的个数 for (int i = now; i

bzoj4556: [Tjoi2016&amp;Heoi2016]字符串 (后缀数组加主席树)

题目是给出一个字符串,每次询问一个区间[a,b]中所有的子串和另一个区间[c,d]的lcp最大值,首先求出后缀数组,对于lcp的最大值肯定是rank[c]的前驱和后继,但是对于这个题会出现问题,就是题目中有区间的限制. For example: 5 1 aaaab 1 2 3 5 对于这个样例,如果直接找到aab的前驱是 aaab,然后由于区间的原因答案是1,但是如果我们再往前找的话,找到aaaab,答案会变成2.那就出现了错误.考虑一下怎么做可以去除这种影响呢? 我们可以二分一下,首先对于[a

codeforces E. Little Elephant and Strings(广义后缀自动机,Parent树)

传送门在这里. 大意: 给一堆字符串,询问每个字符串有多少子串在所有字符串中出现K次以上. 解题思路: 这种子串问题一定要见后缀自动机Parent树Dfs序统计出现次数都是套路了吧. 这道题统计子串个数,那么可以发现,若一个节点所对应的子串出现了K次,那么其贡献就是len,不需要考虑重复. 因为即使出现重复也是在两个位置. 那么只需统计以每个点结束的子串就好了. 之前的Dfs序就很套路了. 只需再跑一遍字符串,更新答案就好了. 代码: 1 #include<cstdio> 2 #include

[BZOJ4556][TJOI2016&amp;&amp;HEOI2016]字符串(二分答案+后缀数组+RMQ+主席树)

4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1360  Solved: 545[Submit][Status][Discuss] Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了 一个长为n的字符串s,和m个问题.佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CE O,嫁给高富帅,走上人生巅

【BZOJ2806】【CTSC2012】Cheat 广义后缀自动机+二分+Dp

题目 题目在这里 思路&做法 我们先对标准作文库建广义后缀自动机. 然后对于每一篇阿米巴的作文, 我们首先把放到广义后缀自动机跑一遍, 对于每一个位置, 记录公共子串的长度\((\)即代码和下文中的\(val\)数组\()\) 接着我们二分答案, 用DP检验. Dp方程很好想, \(d_i = max \{ d_j + i - j \ | \ i-val_i <= j <= i-lim \}\) 可以用单点队列优化. 代码 #include <iostream> #incl

【10.10校内测试】【线段树维护第k小+删除】【lca+主席树维护前驱后驱】

贪心思想.将a排序后,对于每一个a,找到对应的删除m个后最小的b,每次更新答案即可. 如何删除才是合法并且最优的?首先,对于排了序的a,第$i$个那么之前就应该删除前$i-1$个a对应的b.剩下$m-i+1$可以删,那么在剩下的b中查找第$m-i+2$小即可.每次做完就删除当前a对应的b. 注意离散化. 还有数组不要开大了.... #include<bits/stdc++.h> #define LL long long using namespace std; void read(int &a

K-th occurrence HDU - 6704 (SA, 主席树)

大意: 给定串$s$, $q$个询问$(l,r,k)$, 求子串$s[l,r]$的第$k$次出现位置. 本来是个简单签到题, 可惜比赛的时候还没学$SA$...... 好亏啊 相同的子串在$SA$中是一定是连续的一段$[L,R]$ 满足对于$L<i\le R$都有$h_i\ge r-l+1$ 可以先用线段树二分出$L,R$, 然后主席树查询第$k$大即可 #include <iostream> #include <algorithm> #include <cstdio&

2014上海网络预选赛1003(树链剖分)HDU5044

Tree Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 700    Accepted Submission(s): 145 Problem Description You are given a tree (an acyclic undirected connected graph) with N nodes. The tree