• HDU-6704 K-th occurrence(后缀数组+主席树)


    题意

    给一个长度为n的字符串,Q次询问,每次询问((l,r,k)) , 回答子串(s_ls_{l+1}cdots s_r)(k) 次出现的位置,若不存在输出-1。(nle 1e5,Qle 1e5)

    分析

    查询子串第 k 次出现的位置,很容易想到要用处理字符串的有力工具——后缀数组。

    那么该怎么用呢?我们先把样例的字符串的每个后缀排个序,然后对样例进行模拟

    原串:aaabaabaaaab

    排名 后缀 位置
    1 aaaab 8
    2 aaab 9
    3 aaabaabaaab 1
    4 aab 10
    5 aabaaaab 5
    6 aabaabaaab 2
    7 ab 11
    8 abaaaab 6
    9 abaabaaaab 3
    10 b 12
    11 baaaab 7
    12 baabaaaab 4

    查询:[3,3], k = 4

    [3,3]表示子串为 (a) ,我们可以找到起始位置为 3 的后缀 (t = abaabaaab) ,该后缀的第一个字符代表了当前要查询的子串,惊奇的发现,该子串又同时出现在了其他的一些后缀中,而这些后缀与(t) 的LCP(最长公共前缀)大于等于 1 。在这个例子中我们可以发现排名在9之前的后缀与 t 的LCP都大于1,所以只需要在这些后缀的开始位置中找第 k 大的即可。也就是在[8,9,1,10,5,2,11,6,3] 中找第 4 大,即 5.

    查询:[2,3], k = 2

    [2,3] 表示子串为(aa), 起始位置为2的后缀(t = aabaabaaab) , 与 (t) LCP 大于等于2的后缀的开始位置有[8,9,1,10,5,2] , 第2大的位置就是2。

    那么怎么体现在程序中呢?

    求出后缀数组的 (rank,height) 数组,利用(ST)表可以(O(1)) 查询两个后缀的LCP。

    另外可以发现在后缀排名中,排名为 x 的后缀与其他后缀的LCP随着排名之差绝对值增大而减小,所以可以两次二分在排名中找到一个区间,使得这个区间内的所有后缀与目标后缀的LCP都大于等于查询的子串的长度。

    找到这个区间之后,利用可持久化线段树找第 k 大值(对于sa数组)即可

    复杂度分析:求后缀数组(O(nlog(n))) ,二分(O(nlog(n))) , 主席树查询第k大值(O(nlog(n)))

    总复杂度(O(nlog(n)))

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    const int MAXN = N;
    char s[N];
    int sa[N],x[N],y[N],c[N],rk[N],h[N],n,q;
    int len, cnt;
    int a[MAXN];
    int b[MAXN];
    int t[MAXN];
    int ls[MAXN * 40];
    int rs[MAXN * 40];
    int sum[MAXN * 40];
    int build(int l, int r) {
        int rt = ++cnt;
        int mid = l + r >> 1;
        sum[rt] = 0;
        if(l < r) {
            ls[rt] = build(l, mid);
            rs[rt] = build(mid + 1, r);
        }
        return rt;
    }
    int add(int o, int l, int r, int k) {
        int rt = ++cnt;
        int mid = l + r >> 1;
        ls[rt] = ls[o]; rs[rt] = rs[o]; sum[rt] = sum[o] + 1;
        if(l < r)
        if(k <= mid) ls[rt] = add(ls[o], l, mid, k);
        else rs[rt] = add(rs[o], mid + 1, r, k);
        return rt;
    }
    
    int query(int ql, int qr, int l, int r, int k) {
        int x = sum[ls[qr]] - sum[ls[ql]];
        int mid = l + r >> 1;
        if(l == r) return l;
        if(x >= k) return query(ls[ql], ls[qr], l, mid, k);
        else return query(rs[ql], rs[qr], mid + 1, r, k - x);
    }
    
    void build_sa(char *s,int n,int m){
        memset(c,0,sizeof c);
        for(int i=1;i<=n;++i) ++c[x[i] = s[i]];
        for(int i=2;i<=m;++i) c[i] += c[i-1];
        for(int i=n;i>=1;--i) sa[c[x[i]]--] = i;
        for(int k=1;k<=n;k<<=1){
            int p = 0;
            for(int i=n-k+1;i<=n;++i) y[++p] = i;
            for(int i=1;i<=n;++i) if(sa[i] > k) y[++p] = sa[i]-k;
            for(int i=1;i<=m;++i) c[i] = 0;
            for(int i=1;i<=n;++i) ++c[x[i]];
            for(int i=2;i<=m;++i) c[i] += c[i-1];
            for(int i=n;i>=1;--i) sa[c[x[y[i]]]--] = y[i] , y[i] = 0;
            swap(x,y);
            x[sa[1]] = 1; p = 1;
            for(int i=1;i<=n;++i)
                x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i] + k] == y[sa[i-1]+k] ? p : ++p);
            if(p >= n)break;
            m = p;
        }
    }
    void get_height(){
        int k = 0;
        for(int i=1;i<=n;++i)rk[sa[i]] = i;
        for(int i=1;i<=n;++i){
            if(rk[i] == 1)continue;
            if(k) --k;
            int j = sa[rk[i]-1];
            while(j + k <= n && i + k <= n && s[i+k] == s[j+k])++k;
            h[rk[i]] = k;
        }
    }
    int mm[N];
    int best[20][N];
    void initRMQ(int n){
        mm[0] = -1;
        for(int i=1;i<=n;i++)
            mm[i] = ((i & (i-1)) == 0) ? mm[i-1] + 1 : mm[i-1];
        for(int i=1;i<=n;i++)best[0][i] = i;
        for(int i=1;i<=mm[n];i++)
            for(int j=1;j+(1<<i)-1<=n;j++){
                int a = best[i-1][j];
                int b = best[i-1][j+(1<<(i-1))];
                if(h[a] < h[b])best[i][j] = a;
                else best[i][j] = b;
            }
    }
    int askRMQ(int a,int b){
        int t = mm[b-a+1];
        b -= (1<<t) - 1;
        a = best[t][a];b = best[t][b];
        return h[a] < h[b] ? a : b;
    }
    int lcp(int a,int b){
        if(a == b)return n;
        if(a > b)swap(a,b);
        return h[askRMQ(a+1,b)];
    }
    int getL(int l,int r,int len,int x){
        while(l < r){
            int mid = l + r >> 1;
            if(lcp(mid,x) < len) l = mid + 1;
            else r = mid;
        }
        return l;
    }
    int getR(int l,int r,int len,int x){
        while(l < r){
            int mid = (l + r + 1) >> 1;
            if(lcp(mid,x) < len) r = mid - 1;
            else l = mid;
        }
        return l;
    }
    int getAns(int l,int r,int k){
        return query(t[l - 1], t[r], 1, n, k);
    }
    int solve(int l,int r,int k){
        int len = r - l + 1;
        int L = getL(1,rk[l],len,rk[l]);//二分找区间左端点
        int R = getR(rk[l],n,len,rk[l]);//二分找区间右端点
        if(k > R-L+1) return -1;
        return getAns(L,R,k);//返回主席树查询结果
    }
    int main(){
        int T;scanf("%d",&T);
        while(T--){
            scanf("%d%d",&n,&q);
            scanf("%s",s+1);
            build_sa(s,n,150);
            get_height();
            initRMQ(n);
            //初始化主席树
            cnt = 0;
            t[0] = build(1,n);
            for(int i=1;i<=n;i++){
                int tt = sa[i];
                t[i] = add(t[i-1],1,n,tt);
            }
            while(q --){
                int l,r,k;
                scanf("%d%d%d",&l,&r,&k);
                printf("%d
    ",solve(l,r,k));
            }
        }
        return 0;
    }
    
  • 相关阅读:
    Nodejs学习笔记(4) 文件操作 fs 及 express 上传
    Nodejs学习笔记(3) 创建服务器:Web 模块(http)与 express 框架
    Nodejs学习笔记(2) 阻塞/非阻塞实例 与 Nodejs事件
    VS code自定义用户代码片段snippet
    Nodejs学习笔记(1) Nodejs安装+借助express模块简单部署服务器
    jQuery学习笔记(1) 初识jQuery
    jQuery学习笔记(2) jQuery选择器
    第八章 Python之常用模块
    selenium元素和浏览器操作
    selenium元素定位
  • 原文地址:https://www.cnblogs.com/1625--H/p/11403199.html
Copyright © 2020-2023  润新知