「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 的四种求法的更多相关文章

  1. [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

    题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...

  2. luogu P4482 [BJWC2018] Border 的四种求法 - 后缀数组

    题目传送门 传送门 题目大意 区间border. 照着金策讲稿做. Code /** * luogu * Problem#P4482 * Accepted * Time: 8264ms * Memor ...

  3. [BJWC2018]Border 的四种求法

    description luogu 给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\). solution 我们考虑转化题面:给定\ ...

  4. luogu P4482 [BJWC2018]Border 的四种求法

    luogu 对于每个询问从大到小枚举长度,哈希判断是否合法,AC 假的(指数据) 考虑发掘border的限制条件,如果一个border的前缀部分的末尾位置位置\(x(l\le x < r)\)满 ...

  5. 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

    原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...

  6. 【LuoguP4482】[BJWC2018]Border 的四种求法

    题目链接 题意 区间 boder \(n,q\leq 2*10^5\) Sol (暴力哈希/SA可以水过) 字符串区间询问问题,考虑用 \(SAM\) 解决. boder相当于是询问区间 \([l,r ...

  7. 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)

    这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...

  8. P4480 「BJWC2018」「网络流与线性规划24题」餐巾计划问题

    刷了n次用了奇淫技巧才拿到rk1,亥 这道题是网络流二十四题中「餐巾计划问题」的加强版. 于是怀着试一试的心情用费用流交了一发: 哇塞,过了9个点!(强烈谴责出题人用*造数据 下面是费用流解法简述: ...

  9. 「MoreThanJava」Day2:变量、数据类型和运算符

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

随机推荐

  1. MVC的BundleConfig类

    在BundleConfig.cs文件中,包含了一些应用程序中使用的脚本和样式表的文件路径.并可以使用通配符.示例如下: using System.Web; using System.Web.Optim ...

  2. mysql中使用行号

    SELECT `table`.*, (@rownum := @rownum + 1) AS ROWNUMFROM `table` inner join (SELECT @rownum := 0) rW ...

  3. 【小程序】<image>图片实现宽度100%时,高度自适应

    *.wxss样式设置 .img{ width:100% } *.wxml给<image>标签添加属性  mode="widthFix" <image class= ...

  4. 云计算背后的秘密:NoSQL诞生的原因和优缺点

    转载收藏一篇对nosql讲解的比较全面的文章:http://blog.csdn.net/xlgen157387/article/details/47908797 这篇文章将和大家聊聊为什么NoSQL会 ...

  5. 【来龙去脉系列】AutoMapper一款自动映射框架

    前言 通常在一个应用程序中,我们开发人员会在两个不同的类型对象之间传输数据,通常我们会用DTOs(数据传输对象),View Models(视图模型),或者直接是一些从一个service或者Web AP ...

  6. 类加载, 静态变量初始化, String不可变, 泛型使用, 内部类

    1.java变量类型 java变量类型分:基本数据类型变量和Object数据类型变量,变量也是占用者内存的 例如: int i = 3; i这个变量保存的就是整形3, 占32位 Object a = ...

  7. 前端页面loading效果(CSS实现)

    当首页内容或图片比较多时,加载时间会比较长,此时可能出现页面白屏的情况,用户体验较差.所以,在页面完全加载出来之前,可以考虑加入loading效果,当页面完全加载完后,是loading消失即可. 1. ...

  8. 移动端jq及zepto事件绑定

    最近做移动端网页,用到了zepto.js , 其大致用法跟 jquery 差不多,但是在时间绑定的时候被困了好久的坑. 这里说的主要是给未来元素绑定事件.未来元素:这里指的是通过 ajax 请求得到数 ...

  9. 阿里云配置ssl证书

    一.申请证书和下载证书(阿里云申请) 二.在nginx服务器上配置ssl证书 1.检查服务器是否安装openssl 2.在nginx conf 文件夹创建 cret 文件,放置证书 [root@web ...

  10. 区块链Hyperledger Fabric 学习记录(一)开发环境搭建(ubuntu16.04/ubuntu18.04)

    目录 Fabric开发环境搭建 更新说明 教程环境及软件版本 Docker 安装Docker 配置用户组 配置Aliyun Docker加速器 安装docker-compose Go 下载源码 安装源 ...