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;
}
HDU-6704 K-th occurrence(后缀数组+主席树)的更多相关文章
- HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)
题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...
- [2019CCPC网络赛][hdu6704]K-th occurrence(后缀数组&&主席树)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6704 题意为查询子串s[l...r]第k次出现的位置. 写完博客后5分钟的更新 写完博客才发现这份代码 ...
- BZOJ3473:字符串(后缀数组,主席树,二分,ST表)
Description 给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串? Input 第一行两个整数n,k. 接下来n行每行一个字符串. Output 一 ...
- [BZOJ4556][Tjoi2016&Heoi2016]字符串 后缀数组+主席树
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MB Description 佳媛姐姐过生日的时候,她的小 ...
- LOJ_#2720. 「NOI2018」你的名字 _后缀数组+主席树+倍增
题面: https://loj.ac/problem/2720 考虑枚举T串的每个后缀i,我们要做两件事. 一.统计有多少子串[i,j]在S中要求位置出现. 二.去重. 第二步好做,相当于在后缀数组上 ...
- P5346 【XR-1】柯南家族(后缀数组+主席树)
题目 P5346 [XR-1]柯南家族 做法 聪明性是具有传递性的,且排列是固定的 那么先预处理出每个点的名次,用主席树维护\(k\)大值 一眼平衡树,遍历的同时插入\(O(log^2n)\),总时间 ...
- BZOJ 5496: [2019省队联测]字符串问题 (后缀数组+主席树优化建图+拓扑排序)
题意 略 分析 考场上写了暴力建图40分溜了-(结果只得了30分) 然后只要优化建边就行了 首先给出的支配关系无法优化,就直接A向它支配的B连边. 考虑B向以B作为前缀的所有A连边,做一遍后缀数组,两 ...
- [HEOI2016] 字符串 - 后缀数组,主席树,ST表,二分
[HEOI2016] 字符串 Description 给定一个字符串 \(S\), 有 \(m\) 个询问,每个询问给定参数 \((a,b,c,d)\) ,求 \(s[a..b]\) 的子串与 \(s ...
- BZOJ4556:[TJOI\HEOI2016]字符串(后缀数组,主席树,二分,ST表)
Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一个长为n的字符串s,和m个问题.佳媛姐姐必须正确回答这m个问题,才能打开箱 ...
随机推荐
- Both Dolby Atmos driver and API need to be installed问题的一个解决方法
问题的原因在于缺少以下两个部分: Dolby Atmos driver:指你的声卡驱动中自带的杜比文件 如果驱动里没有,说明你的硬件可能不支持杜比,或者驱动太老没有包含杜比. Dolby Atmos ...
- 剑指offer 面试题7:重建二叉树
题目描述 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7, ...
- web网上书店总结(jsp+servlet)
web网上书店总结 前端的首页.效果如下: 基本上按照页面有的内容对其实现功能.按照用户划分功能模块,有后台管理员和普通用户,登录的时候会判断账户的类别,例如0权限代表普通用户登录,1权限代表管理员登 ...
- maven仓库和镜像
目录 简介 本地仓库 远程仓库 远程仓库的更新 远程仓库的认证 部署到远程仓库 快照版本 依赖解析 镜像 本文主要是针对<maven实战>书中关键知识点的学习记录,未免有纰漏或描述不到之处 ...
- cursor pin s和cursor pin s wait on x
1.cursor pin s是一个共享锁,一般情况下是因为发生在SQL短时间内大量执行 案例:在生产库中,突然出现大量的cursor pin s的等待,询问是否有动作后,同事说有编译存储过程(被误导了 ...
- 使用CDN访问免备案网站
如何使用CDN绕过服务器域名备案 前言 不得不说,大陆需要备案,时间真的有点长,至少得5天~20天起步,对于我们这些火急火燎的站长还是比较难受的.这里教大家如何使用cdn绕过备案, 访问速度很快,亲测 ...
- 关于Mysql数据库建库字符集utf8mb4下,排序规则utf8mb4_bin和utf8mb4_general_ci选择造成的查询匹配大小写问题
场景描述: 项目采用了分库模式进行不同业务的开发,在共有的功能模块进行设计的时候采用主从库,或者各分库之中存在同样的库表结构,在使用过程中做库表同步的时候一定要保证库表所在的数据库的字符集和编码格式是 ...
- 微信小程序代码上传,审核发布小程序
1.打开微信开发者工具 管理员扫码 -> 填写好小程序的项目目录.AppID(必须是客户已注册好的AppID).项目名称 2.在app.js中修改id(客户登录后台管理系统的id),app.js ...
- jQuery 勾选显示
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Bitter.Core系列十一:Bitter ORM NETCORE ORM 全网最粗暴简单易用高性能的 NETCore 之 字段变更收集器
有时候我们业务层需要记录 数据库表更改之前的值和更改之后的值的记录集合--此过程在 Bitter.Core 中有强有力的支持.Bitter.Core 字段收集器提供了方便简单易用的 收集对象在修改之前 ...