CF666E Forensic Examination——SAM+线段树合并+倍增
题目大意
给你一个串\(S\)以及一个字符串数组\(T[1...m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l...p_r]\)在\(T[l...r]\)中的哪个串里的出现次数最多,并输出出现次数。
如有多解输出最靠前的那一个。
思路
第一次见到在\(parent tree\)上线段树合并的题,感觉好妙
先对\(T\)建一个广义后缀自动机,考虑对\(SAM\)上的每一个结点建一颗线段树,值域为\([1,m]\),维护出现次数最多的串的位置和次数。又因为\(endpos\)集合(好像也叫\(right\)集合)有这么一个性质:一个结点的\(endpos\)集合即为其在\(parent\ tree\)上子结点的并集,所以我们在建树时只需要上一个线段树合并即可。
上面的那个思路貌似是个套路?
然后来处理询问,显然我们只需要在\(S[p_l...p_r]\)对应的结点的线段树上查\(l-r\)的最大值就行了,但如果直接拿\(S[p_l...p_r]\)在\(SAM\)上匹配,复杂度绝壁不对QwQ。于是我们考虑先把整个\(S\)在\(SAM\)上匹配,需要查哪个子串时通过跳\(suflink\)来找。具体一下,就是对于\(S\)的一个前缀\(S[1...j]\),如果它最后匹配到了结点\(u\),匹配的长度为\(len\),然后我们要查的子串是\(S[i...j]\),就从\(u\)开始跳\(suflink\)直到一个\(maxlen\)大于等于\(j-i+1\)且深度最小的结点,记其为\(v\),要查的就是\(v\)那棵线段树的答案
最后发现跳\(suflink\)的过程可以用倍增来优化,然后就没了
吐槽1.为什么我写离线的就会\(WA\),在线的就过了
吐槽2.下午三点多写完,然后\(CF\)
咕到了六点多,然后交了一发,\(WA\)了,我...
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <map>
#include <set>
using namespace std;
#define ull unsigned long long
#define pii pair<int, int>
#define uint unsigned int
#define mii map<int, int>
#define lbd lower_bound
#define ubd upper_bound
#define INF 0x3f3f3f3f
#define IINF 0x3f3f3f3f3f3f3f3fLL
#define vi vector<int>
#define ll long long
#define mp make_pair
#define pb push_back
#define re register
#define il inline
#define MAXS 500000
#define M 50000
#define Q 500000
#define MAXT 100000
#define LIM 16
char S[MAXS+5], T[MAXT+5];
int n, m, q;
int nxt[26][2*MAXT+5], maxlen[2*MAXT+5], link[2*MAXT+5], in[2*MAXT+5], nid1, lst;
int nid2, root[2*MAXT+5], ch[2][160*MAXT+5];
vi G[2*MAXT+5];
int f[2*MAXT+5][LIM+1];
int tar[MAXS+5], ml[MAXS+5];
struct Data {
int w, pos;
friend Data operator + (Data lhs, Data rhs) {
if(lhs.w > rhs.w) return lhs;
else {
if(lhs.w == rhs.w && lhs.pos < rhs.pos) return lhs;
else return rhs;
}
}
bool operator < (const Data &rhs) const {
return w == rhs.w ? pos > rhs.pos : w < rhs.w;
}
}nodes[160*MAXT+5];
void init() {
nid1 = lst = 1;
nid2 = 0;
}
void pushup(int o) {
nodes[o] = nodes[ch[0][o]]+nodes[ch[1][o]];
}
void add(int &u, int l, int r, int x) {
if(!u) u = ++nid2;
if(l == r) {
nodes[u] = Data{++nodes[u].w, nodes[u].pos = l};
return ;
}
int mid = (l+r)>>1;
if(x <= mid) add(ch[0][u], l, mid, x);
else add(ch[1][u], mid+1, r, x);
pushup(u);
}
int merge(int x, int y, int l, int r) {
if(!x || !y) return x | y;
int now = ++nid2;
if(l == r) {
nodes[now] = Data{nodes[x].w+nodes[y].w, nodes[x].pos};
return now;
}
int mid = (l+r)>>1;
ch[0][now] = merge(ch[0][x], ch[0][y], l, mid);
ch[1][now] = merge(ch[1][x], ch[1][y], mid+1, r);
pushup(now);
return now;
}
Data query(int o, int l, int r, int L, int R) {
if(!o) return Data{0, 0};
if(L <= l && r <= R) return nodes[o];
int mid = (l+r)>>1;
Data ret{0, 0};
if(L <= mid) ret = ret+query(ch[0][o], l, mid, L, R);
if(R > mid) ret = ret+query(ch[1][o], mid+1, r, L, R);
return ret;
}
void extend(int c, int id) {
int cur = ++nid1;
maxlen[cur] = maxlen[lst]+1;
add(root[cur], 1, m, id);
while(lst && !nxt[c][lst]) nxt[c][lst] = cur, lst = link[lst];
if(!lst) link[cur] = 1;
else {
int p = lst, q = nxt[c][lst];
if(maxlen[q] == maxlen[p]+1) link[cur] = q;
else {
int clone = ++nid1;
maxlen[clone] = maxlen[p]+1;
link[clone] = link[q], link[q] = link[cur] = clone;
for(int i = 0; i < 26; ++i) nxt[i][clone] = nxt[i][q];
while(p && nxt[c][p] == q) nxt[c][p] = clone, p = link[p];
}
}
lst = cur;
}
void insert(int id) {
int t = strlen(T+1);
lst = 1;
for(int i = 1; i <= t; ++i) extend(T[i]-'a', id);
}
void build(int u, int fa) {
f[u][0] = fa;
for(int i = 1; i <= LIM; ++i) f[u][i] = f[f[u][i-1]][i-1];
for(int i = 0, v; i < G[u].size(); ++i) {
v = G[u][i];
build(v, u);
root[u] = merge(root[u], root[v], 1, m);
}
}
void pre() {
n = strlen(S+1);
int u = 1, len = 0;
for(int i = 1; i <= n; ++i) {
if(nxt[S[i]-'a'][u]) u = nxt[S[i]-'a'][u], len++;
else {
while(u && !nxt[S[i]-'a'][u]) u = link[u];
if(!u) u = 1, len = 0;
else len = maxlen[u]+1, u = nxt[S[i]-'a'][u];
}
tar[i] = u, ml[i] = len;
}
}
int main() {
scanf("%s%d", S+1, &m);
init();
for(int i = 1; i <= m; ++i) scanf("%s", T+1), insert(i);
for(int i = 2; i <= nid1; ++i) G[link[i]].pb(i);
build(1, 0);
pre();
scanf("%d", &q);
for(int i = 1, l, r, pl, pr, L; i <= q; ++i) {
scanf("%d%d%d%d", &l, &r, &pl, &pr);
L = pr-pl+1;
if(L > ml[pr]) printf("%d 0\n", l);
else {
int u = tar[pr];
for(int k = LIM; ~k; --k) if(maxlen[f[u][k]] >= L) u = f[u][k];
Data ret = query(root[u], 1, m, l, r);
if(ret.w == 0) ret.pos = l;
printf("%d %d\n", ret.pos, ret.w);
}
}
return 0;
}
CF666E Forensic Examination——SAM+线段树合并+倍增的更多相关文章
- CF666E Forensic Examination SAM+线段树合并+前缀树倍增
$ \color{#0066ff}{ 题目描述 }$ 给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l. ...
- loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增
题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...
- CF666E Forensic Examination SAM+倍增,线段树和并
题面: 给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[p_l..p_r]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数.如有多解输出最靠前的那一个. 分析: 第 ...
- CF666E-Forensic Examination【广义SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/CF666E 解题思路 给出一个串\(S\)和\(n\)个串\(T_i\).\(m\)次询问\(S_{a\sim b} ...
- 字符串(tjoi2016,heoi2016,bzoj4556)(sam(后缀自动机)+线段树合并+倍增+二分答案)
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了 一个长为\(n\)的字符串\(s\),和\(m\)个问题.佳媛姐姐必须正确回答这\(m\)个问题, ...
- 2019.02.27 bzoj4556: [Tjoi2016&Heoi2016]字符串(二分答案+sam+线段树合并)
传送门 题意:给一个字符串SSS. 有mmm次询问,每次给四个参数a,b,c,da,b,c,da,b,c,d,问s[a...b]s[a...b]s[a...b]的所有子串和s[x...y]s[x... ...
- CF700E Cool Slogans——SAM+线段树合并
RemoteJudge 又是一道用线段树合并来维护\(endpos\)的题,还有一道见我的博客CF666E 思路 先把\(SAM\)建出来 如果两个相邻的串\(s_i\)和\(s_{i+1}\)要满足 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- UOJ#395. 【NOI2018】你的名字 字符串,SAM,线段树合并
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ395.html 题解 记得同步赛的时候这题我爆0了,最暴力的暴力都没调出来. 首先我们看看 68 分怎么做 ...
随机推荐
- Red And Green
#include <stdio.h> #include <string.h> #define LENGTH 50 /* * 1.字符序列中有一个字符肯定是分界点,它的左边全为红 ...
- lnmp 命令 及其 TP5 部署遇到的一些问题
1.添加站点域名命令: lnmp vhost add; 2.重置mysql密码: 第一种方法:用军哥的一键修改LNMP环境下MYSQL数据库密码脚本 一键脚本肯定是非常方便.具体执行以下命令: wge ...
- 31.网络协议介绍tcp/udp
网络协议 TCP:网络中传输数据的协议,打电话 解决了我可能在网络中找不到别人(数据无法传输到) 保证数据传输的稳定性,可靠性 保证数据的安全性,完整性 对方要有响应 尝试重新发送 UDP:传输数据的 ...
- (十五)mybatis 逆向工程
目录 为什么需要逆向工程 使用方法 如何读懂生成的代码 总结 为什么需要逆向工程 对于数据库中的那么多的表 ,基本的 CRUD 操作 ,以及 mybatis 需要使用的 接口.mapper ,这些工作 ...
- Ubuntu 下开发ARM
1. 准备工作 linux下自带虚拟串口的驱动,不需要手动安装.CP2102之类的USB转串口,是ttyUSBx. 所有的设备都在/dev目录下,简单扫描串口的办法: ls /dev > bef ...
- 剑指offer21:第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。(注意:这两个序列的长度是相等的)
1 题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是 ...
- C++ STL String学习 (待续)
头文件:<string> 字符串类初始化: string s1="aabbba"; s2=string("bbb"); string s3=stri ...
- python之并发编程(概念篇)
一.进程 1.什么是进程 进程是正在进行的一个过程或者一个任务.而负责执行任务的则是cpu. 2.进程与程序的区别 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程 ...
- 第二章、http协议及嗅探抓包--http协议详解
初识http协议 hypertext trandfer protocol 超文本传输协议,是一种分布式,合作式,多媒体信息系统服务,面向应用层的协议.使用最广泛的应用层协议,基于传输层的TCP协 ...
- 怎样在浏览器端增加一条Cookie
可以使用 document.cookie, 这个属性可读可写, 读时是读取所有没有设置HttpOnly的cookie作为一个字符串返回, 写时是将一个cookie写入到document.cookie中 ...