「BJWC2018」Border 的四种求法
「BJWC2018」Border 的四种求法
题目描述
给一个小写字母字符串 \(S\) ,\(q\) 次询问每次给出 \(l,r\) ,求 \(s[l..r]\) 的 Border 。
\(1 \leq n,q \leq 10^5\)
解题思路 :
求 Border 等价于在 \([l, r)\) 中找一个点 \(i\) ,满足 \(lcs(i, r) \geq i -l + 1\) ,且 \(i\) 最大。
考虑把问题丢到 \(\text{Sam}\) 上,那么满足的条件变为 \(len(lca(i,r)) > i - l\) ,对于 \(r\) 的每一个祖先算出其作为 \(lca\) 时最优的 \(i\) ,可以对 \(\text{parent}\) 树进行轻重链剖分,对于重链和轻重链切换时候的贡献分类讨论。
对于重链上的点,其要作为 \(lca\) 的话 \(i\) 一定是其本身或者一个来自轻子树里面的点,于是可以暴力把每一个点所连出去的轻子树的点的贡献加在这个点上,考虑一个点被加的次数等同于轻重链切换的次数,复杂度没有问题。
考虑把重链上的点的贡献并在一起后怎么求这条重链上能得到的最大的 \(i\) ,将条件移一下项转变为 \(i-len(lca(i,r))<l\) ,那么问题就变为找出 \(i-len(x) < l\) 且 \(i < r\) 最大的 \(i\) 。其中 \(x\) 是重链上的一个点,\(i\) 是其轻子树里面的一个点。仔细观察发现这个东西可以对于每一条重链维护一颗以 \(i\) 为下标的线段树,直接在线段树上二分即可。
考虑轻重链切换的情况,对于跳上去新的重链的第一个点 \(x\),其除了之前那条重链所在的子树的点都可以和 \(r\) 产生 \(lca(r, i)=x\) ,这里直接统计的话复杂度就爆炸了。但是可以观察到,对于之前那条重链所在的点,必然会在之前的某个点 \(y\) 被算到过,而 \(\text{parent}\) 树上 \(len\) 是随深度递增的,如果在 \(x\) 这里再被算一次不会产生更优的答案。所以可以直接统计子树里面的所有情况,用再对每一个点开一个线段树并用线段树合并维护即可
实际上一条重链并不是所有点都会产生贡献,只有当前跳到的点 \(u\) 以上的点的贡献才能被算入答案。所以把询问离线下来拆成若干段重链的形式。最后 \(\text{dfs}\) 一遍边加贡献边回答每一段的询问,最后合并每一段的答案求出最终的结果。总复杂度 \(O(nlog^2n)\)
/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int ch = 0, f = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
#define mk make_pair
#define fi first
#define se second
const int N = 500005;
char s[N];
int pos[N], Ans[N], m;
struct Node{ int id, l, r; }; vector<Node> q[N];
struct SegmentTree{
int val[N*25], lc[N*25], rc[N*25], rt[N*10], size;
inline SegmentTree(){ memset(val, 127, sizeof(val)); }
inline void ins(int &u, int l, int r, int pos, int x){
if(!u) u = ++size;
if(l == r) return (void) (val[u] = min(val[u], x));
int mid = l + r >> 1;
if(pos <= mid) ins(lc[u], l, mid, pos, x);
else ins(rc[u], mid + 1, r, pos, x);
val[u] = min(val[lc[u]], val[rc[u]]);
}
inline int query(int u, int l, int r, int pos, int lim){
if(!u) return 0;
if(l == r) return val[u] < lim ? l : 0;
int mid = l + r >> 1, res = 0;
if(mid + 1 >= pos) return query(lc[u], l, mid, pos, lim);
if(val[rc[u]] < lim) res = query(rc[u], mid + 1, r, pos, lim);
if(!res) return query(lc[u], l, mid, pos, lim); else return res;
}
inline int merge(int x, int y, int l, int r){
if(!x || !y) return x + y;
int o = ++size, mid = l + r >> 1;
if(l == r) val[o] = min(val[x], val[y]);
else{
lc[o] = merge(lc[x], lc[y], l, mid);
rc[o] = merge(rc[x], rc[y], mid + 1, r);
val[o] = min(val[lc[o]], val[rc[o]]);
}
return o;
}
}T1, T2;
namespace Sam{
vector<int> g[N];
vector< pair<int, int> > s[N];
int sz[N], top[N], ms[N];
int ch[N][26], len[N], fa[N], id[N], size = 1, tail = 1;
inline int newnode(int x){ return len[++size] = x, size; }
inline void ins(int c, int x){
int p = tail, np = newnode(len[p] + 1);
pos[x] = np, id[np] = x;
for(; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
if(!p) return (void) (fa[np] = 1, tail = np);
int q = ch[p][c];
if(len[q] == len[p] + 1) fa[np] = q;
else{
int nq = newnode(len[p] + 1);
fa[nq] = fa[q], fa[q] = fa[np] = nq;
for(int i = 0; i < 26; i++) ch[nq][i] = ch[q][i];
for(; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
}tail = np;
}
inline void addedge(){
for(int i = 2; i <= size; i++) g[fa[i]].push_back(i);
}
inline void Buildtree(int u){
if(id[u]) T1.ins(T1.rt[u], 1, size, id[u], id[u]);
sz[u] = 1;
for(int i = 0; i < (int) g[u].size(); i++){
int v = g[u][i]; Buildtree(v);
T1.rt[u] = T1.merge(T1.rt[u], T1.rt[v], 1, size);
sz[u] += sz[v];
if(sz[v] > sz[ms[u]]) ms[u] = v;
}
}
inline void prework(int u, int x){
if(id[u]) s[x].push_back(mk(id[u], id[u] - len[x]));
for(int i = 0; i < (int) g[u].size(); i++) prework(g[u][i], x);
}
inline void dfs(int u, int chain){
top[u] = chain;
if(id[u]) s[u].push_back(mk(id[u], id[u] - len[u]));
if(ms[u]) dfs(ms[u], chain);
for(int i = 0; i < (int) g[u].size(); i++){
int v = g[u][i];
if(v != ms[u]) dfs(v, v), prework(v, u);
}
}
inline void Doit(){ addedge(), Buildtree(1), dfs(1, 1); }
inline int query(int u, int l, int r, Node Query){
int res = T1.query(T1.rt[u], 1, size, r, l + len[u]);
while(u){
q[u].push_back(Query), u = fa[top[u]];
if(!u) break;
res = max(res, T1.query(T1.rt[u], 1, size, r, l + len[u]));
}
return res < l ? 0 : res - l + 1;
}
inline void solve(int u){
for(int i = 0; i < (int) s[u].size(); i++)
T2.ins(T2.rt[top[u]], 1, size, s[u][i].fi, s[u][i].se);
for(int i = 0; i < (int) q[u].size(); i++){
int l = q[u][i].l, r = q[u][i].r;
int now = T2.query(T2.rt[top[u]], 1, size, r, l);
Ans[q[u][i].id] = max(Ans[q[u][i].id], (now < l ? 0 : now - l + 1));
}
for(int i = 0; i < (int) g[u].size(); i++) solve(g[u][i]);
}
}
int main(){
scanf("%s", s + 1); int len = strlen(s + 1);
for(int i = 1; i <= len; i++) Sam::ins(s[i] - 'a', i);
Sam::Doit(); read(m);
for(int i = 1, l, r; i <= m; i++){
read(l), read(r);
Ans[i] = Sam::query(pos[r], l, r, (Node){i, l, r});
}
Sam::solve(1);
for(int i = 1; i <= m; i++) printf("%d\n", Ans[i]);
return 0;
}
「BJWC2018」Border 的四种求法的更多相关文章
- [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)
题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...
- luogu P4482 [BJWC2018] Border 的四种求法 - 后缀数组
题目传送门 传送门 题目大意 区间border. 照着金策讲稿做. Code /** * luogu * Problem#P4482 * Accepted * Time: 8264ms * Memor ...
- [BJWC2018]Border 的四种求法
description luogu 给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\). solution 我们考虑转化题面:给定\ ...
- luogu P4482 [BJWC2018]Border 的四种求法
luogu 对于每个询问从大到小枚举长度,哈希判断是否合法,AC 假的(指数据) 考虑发掘border的限制条件,如果一个border的前缀部分的末尾位置位置\(x(l\le x < r)\)满 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- 【LuoguP4482】[BJWC2018]Border 的四种求法
题目链接 题意 区间 boder \(n,q\leq 2*10^5\) Sol (暴力哈希/SA可以水过) 字符串区间询问问题,考虑用 \(SAM\) 解决. boder相当于是询问区间 \([l,r ...
- 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)
这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...
- P4480 「BJWC2018」「网络流与线性规划24题」餐巾计划问题
刷了n次用了奇淫技巧才拿到rk1,亥 这道题是网络流二十四题中「餐巾计划问题」的加强版. 于是怀着试一试的心情用费用流交了一发: 哇塞,过了9个点!(强烈谴责出题人用*造数据 下面是费用流解法简述: ...
- 「MoreThanJava」Day2:变量、数据类型和运算符
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
随机推荐
- 字典学习(Dictionary Learning, KSVD)详解
注:字典学习也是一种数据降维的方法,这里我用到SVD的知识,对SVD不太理解的地方,可以看看这篇博客:<SVD(奇异值分解)小结 >. 1.字典学习思想 字典学习的思想应该源来实际生活中的 ...
- Hadoop日记Day7---HDFS的WED端口
HDFS 对外提供了可供访问的http server,开放了很多端口,下面介绍常用的几个端口(http://hadoop:……). 一.50070 端口,查看NameNode 状态,如图1.1所示. ...
- *args和**kwargs在python中的作用
我发现PYTHON新手在理解*args和**kwargs这两个魔法变量的时候有些困难.他们到底是什么呢? 首先,我先告诉大家一件事情,完整地写*args和**kwargs是不必要的,我们可以只写*和* ...
- 异常 java.lang.IllegalArgumentException: Result Maps collection already contains value
这是因为用了一次以上(多次)mbg导致sql映射文件堆积导致的异常,删除对应的sql映射文件,然后重新生成即可. Caused by: java.lang.IllegalArgumentExcepti ...
- linux下如何解除被占用的端口号
在本例中,假设8080端口被占用. 1.查看8080端口是否被占用: netstat -anp | grep 8080输出结果:tcp 0 0 :::8080 ...
- NO--12模拟服务器端请求之node.js
最近几天项目上线,工作比较忙,没时间更博了,好在今天有点时间并且同事问道我一个问题,正好一块解决 使用 Vue 写项目肯定会遇到一个问题,如何模拟服务端请求数据,那这就需要用到 node.js 了. ...
- DES的加密与解密算法(Python实现)
DES的加密与解密算法(Python实现) 密码学实验:实现了DES的简单的加密和解密算法,DES算法的相关资料网上很多,这里不再赘述,仅仅贴出源代码给大家分享,源码中包含很多汉字注释,相信大家都是可 ...
- 通过sqli-labs学习sql注入——基础挑战之less1
环境准备: Phpstudy (PHP+Apache+Mysql) Sql-lab 首先了解下基础知识: URL编码: 因为在浏览器中,当我们访问一个网址的时候,浏览器会自动将用户输入的网址进行UR ...
- [T-ARA][놀아볼래?][要玩吗]
歌词来源:http://music.163.com/#/song?id=22704479 作曲 : 赵英秀/김태현 [作曲 : 赵英秀/k/gim-Tae-hyeon] 作词 : 安英民 [作词 : ...
- 第十七次ScrumMeeting博客
第十七次ScrumMeeting博客 本次会议于12月7日(四)22时整在3公寓725房间召开,持续20分钟. 与会人员:刘畅.辛德泰.张安澜.赵奕.方科栋. 1. 每个人的工作(有Issue的内容和 ...