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

题意

给一个长度为n的字符串,Q次询问,每次询问\((l,r,k)\) , 回答子串\(s_ls_{l+1}\cdots s_r\) 第\(k\) 次出现的位置,若不存在输出-1。\(n\le 1e5,Q\le 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\n",solve(l,r,k));
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/1625--H/p/11403199.html

时间: 2024-11-08 03:49:45

HDU-6704 K-th occurrence(后缀数组+主席树)的相关文章

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

hdu 6704 K-th occurrence 二分 ST表 后缀数组 主席树

我们考虑,一个子串必定是某个后缀的前缀. 排序相邻的后缀他们的前缀一定最相似. 所以全部的一种子串必定是一些排序相邻的后缀的公共前缀. 从l开始的子串,则从rank[l]开始看,两侧height保证大于子串长度,能延伸多长,则证明有多少个这种子串. 我们用ST表维护出height的最小值,然后通过最小值二分即可,边界有些棘手. 然后我们就得到了一个height不小于子串长度的连续区间,这个区间是以原后缀的字典序排序的. 而同时,sa数组下标为排序,值为原串位置. 所以我们对这个区间在sa数组上做

hdu6704 后缀数组+主席树+ST +二分

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6704 推荐博客:https://www.cnblogs.com/1625--H/p/11403199.html 题意:输入t,接下来t组数据,每组数据输入n和q,n代表字符串长度,q代表查询次数,接下来一行输入长度为n的字符串,最后再输入q行查询,每一个查询有l,r,k,查询字符串中在区间[l,r]里的这个子串第k次出现的位置,位置就是出现这个子串的首字母位置,如果没有第k次,则输出-1. 比赛时我

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

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

hdu 5030 Rabbit&#39;s String(后缀数组&amp;二分)

Rabbit's String Time Limit: 40000/20000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 288    Accepted Submission(s): 108 Problem Description Long long ago, there lived a lot of rabbits in the forest. One day, the

hdu 5030 Rabbit&#39;s String(后缀数组)

题目链接:hdu 5030 Rabbit's String 题目大意:给定k和一个字符串,要求将字符串拆分成k个子串.然后将每个子串中字典序最大的子串选出来,组成一个包含k个字符串的集合,要求这个集合中字典序最大的字符串字典序最小. 解题思路:网赛的时候试图搞了一下这道题,不过水平还是有限啊,后缀数组也是初学,只会切一些水题.赛后看了一下别人的题解,把这题补上了. 首先对整个字符串做后缀数组,除了处理出sa,rank,height数组,还要处理处f数组,f[i]表示说以0~sa[i]开头共有多少

hdu 3518 Boring counting(后缀数组)

Boring counting                                                                       Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Problem Description 035 now faced a tough problem,his english teacher gives him

hdu 4691 Front compression (后缀数组)

题目大意: 介绍了一种压缩文本的方式,问压缩前后的文本长度. 思路分析: 后缀数组跑模板然后考虑两次l r之间的lcp. 然后减掉重复的长度. 注意ans2的累加. #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #define maxn 200005 using namespace std; typede

【bzoj1146】[CTSC2008]网络管理Network 倍增LCA+dfs序+树状数组+主席树

题目描述 M公司是一个非常庞大的跨国公司,在许多国家都设有它的下属分支机构或部门.为了让分布在世界各地的N个部门之间协同工作,公司搭建了一个连接整个公司的通信网络.该网络的结构由N个路由器和N-1条高速光缆组成.每个部门都有一个专属的路由器,部门局域网内的所有机器都联向这个路由器,然后再通过这个通信子网与其他部门进行通信联络.该网络结构保证网络中的任意两个路由器之间都存在一条直接或间接路径以进行通信. 高速光缆的数据传输速度非常快,以至于利用光缆传输的延迟时间可以忽略.但是由于路由器老化,在这些