题意:给你一个长度为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));
}
} }

  

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

  1. [HEOI2016/TJOI2016]字符串(后缀数组+二分+主席树/后缀自动机+倍增+线段树合并)

    后缀数组解法: 先二分最长前缀长度 \(len\),然后从 \(rnk[c]\) 向左右二分 \(l\) 和 \(r\) 使 \([l,r]\) 的 \(height\geq len\),然后在主席树 ...

  2. 【BZOJ-4556】字符串 后缀数组+二分+主席树 / 后缀自动机+线段树合并+二分

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 657  Solved: 274[Su ...

  3. 【BZOJ4556】[Tjoi2016&Heoi2016]字符串 后缀数组+二分+主席树+RMQ

    [BZOJ4556][Tjoi2016&Heoi2016]字符串 Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一 ...

  4. 2019CCPC网络预选赛 八道签到题题解

    目录 2019中国大学生程序设计竞赛(CCPC) - 网络选拔赛 6702 & 6703 array 6704 K-th occurrence 6705 path 6706 huntian o ...

  5. HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并)

    layout: post title: HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并) author: "luowentaoaa&quo ...

  6. 【BZOJ4556】字符串(后缀数组,主席树)

    [BZOJ4556]字符串(后缀数组,主席树) 题面 BZOJ 题解 注意看题: 要求的是\([a,b]\)的子串和[c,d]的\(lcp\)的最大值 先来一下暴力吧 求出\(SA\)之后 暴力枚举\ ...

  7. 【BZOJ5304】[HAOI2018]字串覆盖(后缀数组,主席树,倍增)

    [BZOJ5304][HAOI2018]字串覆盖(后缀数组,主席树,倍增) 题面 BZOJ 洛谷 题解 贪心的想法是从左往右,能选就选.这个显然是正确的. 题目的数据范围很好的说明了要对于询问分开进行 ...

  8. 【CF666E】Forensic Examination(后缀自动机,线段树合并)

    [CF666E]Forensic Examination(后缀自动机,线段树合并) 题面 洛谷 CF 翻译: 给定一个串\(S\)和若干个串\(T_i\) 每次询问\(S[pl..pr]\)在\(T_ ...

  9. 【BZOJ3413】匹配(后缀自动机,线段树合并)

    [BZOJ3413]匹配(后缀自动机,线段树合并) 题面 BZOJ 题解 很好的一道题目. 做一个转化,匹配的次数显然就是在可以匹配的区间中,每个前缀的出现次数之和. 首先是空前缀的出现次数,意味着你 ...

随机推荐

  1. JavaWeb(四):JDBC

    数据持久化(persistence) 把数据保存到可掉电式存储设备中以供之后使用. 大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各 ...

  2. Oracle分组函数之CUBE

    功能介绍: 首先是进行无字段的聚合,然后依次对每个字段进行聚合 创建表: 插入测试数据: ROLLUP: Select t.classid,t.studentname,Sum(t.score) Fro ...

  3. ARP(Address Resolution Protocol)地址解析协议初识

    ARP址解析协议是根据IP地址获取物理地址的一个TCP/IP协议.它工作在OSI七层模型的中第二层——数据链路层. 使用ARP地址解析协议,可根据网络层IP数据包包头中的IP地址信息解析出目标硬件地址 ...

  4. php 标准库之ArrayObject

    以下为ArrayObject的常用函数: ArrayIterator::current( void ) //返回当前数组元素 ArrayIterator::key(void) //返回当前数组key ...

  5. STM32之光敏电阻传感器模块的使用

    本实验配合2.2寸TFT液晶屏显示,当光弱的时候显示“昏暗”,光强时显示“明亮”. 实验使用的是下图所示的3线光敏电阻传感器模块,用途:光线亮度检测,光线亮度传感器,智能小车寻光模块.模块特色:比较器 ...

  6. java 虚方法。 后面new 那个类, 就调用哪个类的方法 ,而非定义类的方案。 关于父子 类的 呵呵

    java   虚方法.     后面new  那个类, 就调用哪个类的方法 ,而非定义类的方案.  关于父子 类的   呵呵 在多态的情况下,声明为父类类型的引用变量只能调用父类中的方法,但如果此变量 ...

  7. 永远让比较函数对相等的值返回false

    今天在刷OJ的时候,有一道题一直Runtime Error,查错出来是比较函数写挂掉了,但是不知道错误在哪,于是查阅资料:永远让比较函数对相等的值返回false 具体可点击此处查看分析:链接 另外,在 ...

  8. Mac005--VS&webstorm前端开发工具安装

    Mac--Visual studio Code工具安装(企业常用) 安装网址:https://code.visualstudio.com/download 设置格式: 1.配置工作区与终端字体大小 常 ...

  9. tbox协程使用之切换与等待

    tbox的协程实现,是stackfull模式的,需要指定独立堆栈和协程函数,目前暂时还不能像golang那样实现堆栈的动态增长,之后会对其进行支持. 目前提供下面一些功能特性: 1. 提供yield切 ...

  10. 查询SQLSERVER执行过的SQL记录(历史查询记录)(转)

    原文链接:https://www.cnblogs.com/icycore/p/10493237.html 有的时候,需要知道近段时间SQLSERVER执行了什么语句,可以用下面的方法: SELECT ...