【LuoguP4482】[BJWC2018]Border 的四种求法
题意
区间 boder
\(n,q\leq 2*10^5\)
Sol
(暴力哈希/SA可以水过)
字符串区间询问问题,考虑用 \(SAM\) 解决。
boder相当于是询问区间 \([l,r]\) 内满足 \(lcs(i,r)>=i-l+1\) 的最大的 \(i\)
那么首先可以得到一个暴力做法,我们定位 \([1,r]\) 这个串的节点,那么相当于要询问的就是它的祖先。每一个点可以直接把它的 \(len\) 值视为 \(lcs(i,r)\)。这样的话用线段树合并维护出endpos后一个个往上跳就可以用线段树进行询问了,每次找到 \([l,min(l+len-1,r-1)]\) 里的最大的 \(endpos\) 用来更新答案。
就像 \(boder\) 不具有可二分性一样,上面的做法也没有什么优化空间。
这样我们肯定是从离线询问简化查询入手。
我们不能一个个跳 \(parent\) 树那样复杂度必然不对。
考虑到对于一个询问 \([l,r]\) 我们要用的其实是祖先节点的 \(endpos\) 中 \(i-len+1<=l\) 的且在询问区间内的部分。
于是我们想可不可以把祖先的 \(endpos\) 丢在一起查询从而避免一个个往上跳。
方法自然是有的,首先我们发现和询问同一个子树的 \(endpos\) 是没有用的,因为这个 \(endpos\) 在下面一定不会更差。
那么我们就可以想到这样一个方法:
把询问挂在树上后,从上面下来,用线段树维护好外面所有点的 \(i-len+1\),然后到一个点的时候就可以直接用线段树查询是否有合法节点了。
直接这样做显然复杂度爆炸,主要问题在于如果一个点的子树过多,那么一个点被加入删除的次数就爆炸了。
这时我们就可以用上 dsu on tree 来优化了。
本质上其实是轻重链剖分分治。
我们必须要让一个点被加入线段树的次数得到控制。发现一个点会在它的每一个祖先处被加入/删除一次,这个就可以用树链剖分来优化到 \(log\) 次。
我们把询问在每跳一次轻边后都挂在当前点上,然后对整棵树 dfs ,每次处理完重链上的所有轻子树后,从重链顶端往下一次加入轻子树的所有节点,这样到了一个点我们保存的就是除了这个点的子树外的所有子树。而每一个点只会在轻重边切换的时候被加入一次,总共只有 \(log\) 次。
但是这样还有一个问题,当我们的询问跳了一条重链后,到达的点的其他子树也可能贡献答案。
但是我们这样子就会把重儿子也给加进去,那样就凉了,复杂度会挂。
所以为了避免这种情况,我们先在这种节点处用暴力线段树合并的方法直接处理对答案的贡献。
由于询问个数只是多乘一个 \(log\) 总复杂度不变。
时间复杂度为 \(O(nlog^2n)\)
code:
#include<bits/stdc++.h>
using namespace std;
#define Set(a,b) memset(a,b,sizeof(a))
template<class T>inline void init(T&x){
x=0;char ch=getchar();bool t=0;
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
if(t) x=-x;return;
}typedef long long ll;
const int N=2e5+10;
const int MAXN=5e5;
const int MAXM=5e6;
char s[N];
int q,l,r;
int n,node=0,lst=0;
int son[MAXN][26];
int fa[MAXN],len[MAXN],aim[N],size[MAXN],top[MAXN],Son[MAXN],ans[N];
struct edge{int to,next;}a[MAXM];
int head[MAXN],cnt=0,QL[N],QR[N];
inline void add(int x,int y){a[++cnt]=(edge){y,head[x]};head[x]=cnt;}
vector<int> Que[MAXN];
namespace Endpos{
int ls[MAXM],rs[MAXM],Mn[MAXM];int node=0;int rt[MAXN];
inline int chk(int x,int y){if(!x) return y;if(!y) return x;return min(x,y);}
inline void Insert(int&u,int l,int r,int p){
if(!u) u=++node;Mn[u]=chk(Mn[u],p);
if(l==r) return;
int mid=(l+r)>>1;
if(mid>=p) Insert(ls[u],l,mid,p);
else Insert(rs[u],mid+1,r,p);
return;
}
int Merge(int u,int v,int l,int r){
if(!u||!v) return u|v;Mn[u]=chk(Mn[u],Mn[v]);
if(l==r) return u;int mid=(l+r)>>1;
ls[u]=Merge(ls[u],ls[v],l,mid);
rs[u]=Merge(rs[u],rs[v],mid+1,r);
return u;
}
int Query(int u,int l,int r,int L,int R){
if(!u||L>R) return 0;int mid=(l+r)>>1;
if(l==r) return l;
if(L> mid) return Query(rs[u],mid+1,r,L,R);
if(R<=mid) return Query(ls[u],l,mid,L,R);
if(rs[u]&&Mn[rs[u]]<=R) return Query(rs[u],mid+1,r,mid+1,R);
return Query(ls[u],l,mid,L,mid);
}
void dfs(int u){
for(int v,i=head[u];i;i=a[i].next) {
v=a[i].to;dfs(v);
rt[u]=Merge(rt[u],rt[v],1,n);
}
for(int id:Que[u]) {// 重链底部
int l=QL[id],r=QR[id];// i-l+1 <= len[u] => i<= len[u]+l-1
ans[id]=max(ans[id],Query(rt[u],1,n,l,min(r-1,len[u]+l-1))-l+1);
}return;
}
}
int sta[MAXN];
inline void extend(int c){
int u=lst;int p=lst=++node;len[p]=len[u]+1,aim[len[p]]=p;sta[p]=len[p];
Endpos::Insert(Endpos::rt[p],1,n,len[p]);
while(~u&&!son[u][c]) son[u][c]=p,u=fa[u];
if(~u) {
int v=son[u][c];
if(len[v]==len[u]+1) return void(fa[p]=v);
int q=++node;memcpy(son[q],son[v],sizeof(son[v]));
fa[q]=fa[v];len[q]=len[u]+1;fa[v]=fa[p]=q;
while(~u&&son[u][c]==v) son[u][c]=q,u=fa[u];
}return;
}
void dfs1(int u){
size[u]=1;
for(int v,i=head[u];i;i=a[i].next) {
v=a[i].to;dfs1(v);size[u]+=size[v];
if(!Son[u]||size[Son[u]]<size[v]) Son[u]=v;
}return;
}
void dfs2(int u,int tp){
top[u]=tp;if(!Son[u]) return;
dfs2(Son[u],tp);
for(int v,i=head[u];i;i=a[i].next) {
v=a[i].to;if(v==Son[u]) continue;
dfs2(v,v);
}return;
}
inline void Deal(int i){
int p=aim[QR[i]];
while(p>0) Que[p].emplace_back(i),p=fa[top[p]];
return;
}
namespace Chain{//i-l+1 <= len[u] => i-len[u]+1 <= l
const int MAX=2e6;
int Mn[MAX],ls[MAX],rs[MAX];int node=0;
int rt=0;
inline void Insert(int&u,int l,int r,int p,int x){
if(!u) {u=++node;ls[u]=rs[u]=0;Mn[u]=1e9;}
Mn[u]=min(Mn[u],x);
if(l==r) return;
int mid=(l+r)>>1;
if(mid>=p) Insert(ls[u],l,mid,p,x);
else Insert(rs[u],mid+1,r,p,x);
return;
}
void Pushin(int u,int len){
if(sta[u]) Insert(rt,1,n,sta[u],sta[u]-len+1);
for(int v,i=head[u];i;i=a[i].next) {v=a[i].to;Pushin(v,len);}
return;
}
int Query(int u,int l,int r,int L,int R,int x){
if(!u||L>R||Mn[u]>x) return 0;
if(l==r) return l;
int mid=(l+r)>>1;
if(mid>=R) return Query(ls[u],l,mid,L,R,x);
if(mid< L) return Query(rs[u],mid+1,r,L,R,x);
int ret=0;
if(rs[u]&&Mn[rs[u]]<=x) ret=Query(rs[u],mid+1,r,mid+1,R,x);
if(ret) return ret;return Query(ls[u],l,mid,L,mid,x);
}
void dfs(int u) {
for(int now=u;Son[now];now=Son[now]) {
for(int v,i=head[now];i;i=a[i].next) {v=a[i].to;if(v==Son[now]) continue;dfs(v);}
}rt=node=0;int now=u;
do{
for(int v,i=head[now];i;i=a[i].next) {v=a[i].to;if(v==Son[now]) continue;Pushin(v,len[now]);}
if(sta[now]) Insert(rt,1,n,sta[now],sta[now]-len[now]+1);
for(int id:Que[now]) {ans[id]=max(ans[id],Query(rt,1,n,QL[id],QR[id]-1,QL[id])-QL[id]+1);}
now=Son[now];
}while(now);return;
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);init(q);fa[0]=-1;lst=0;
for(int i=1;i<=n;++i) extend(s[i]-'a');
for(int i=1;i<=node;++i) add(fa[i],i);
dfs1(0),dfs2(0,0);
for(int i=1;i<=q;++i) {init(QL[i]),init(QR[i]);Deal(i);}
Endpos::dfs(0);Chain::dfs(0);
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}
【LuoguP4482】[BJWC2018]Border 的四种求法的更多相关文章
- [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)
题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- 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)\)满 ...
- 「BJWC2018」Border 的四种求法
「BJWC2018」Border 的四种求法 题目描述 给一个小写字母字符串 \(S\) ,\(q\) 次询问每次给出 \(l,r\) ,求 \(s[l..r]\) 的 Border . \(1 \l ...
- 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)
这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...
- 四种浏览器对 clientHeight、offsetHeight、scrollHeight、clientWidth、offsetWidth 和 scrollWidth 的解释差异
网页可见区域宽:document.body.clientWidth 网页可见区域高:document.body.clientHeight 网页可见区域宽:document.body.offsetWid ...
- 关于SWT/JFace的事件模型的四种方式
事件的4种写法 1.匿名内部类方式的写法 2.命名内部类的写法 3.外部类写法 4.实现监听接口的写法 第一种用匿名内部类的方法: public class HelloWorld { private ...
随机推荐
- 什么是 redis 的雪崩、穿透和击穿?
缓存雪崩 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机.缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据 ...
- P5441 【XR-2】伤痕
Luogu5441 有 \(n\) 个点 ( \(n\) 为奇数 , \(n \le 99\) ) 的完全图 , 其中可以有最多 \(n\) 条无向边 , 其他都是有向边 . 如果对于某四个点不经过这 ...
- hdoj3534(树形dp,求树的直径的条数)
题目链接:https://vjudge.net/problem/HDU-3534 题意:给出一棵树,求树上最长距离(直径),以及这样的距离的条数. 思路:如果只求直径,用两次dfs即可.但是现在要求最 ...
- flannel vxlan工作基本原理及常见排障方法
写在前面 最近用kubeadm鼓捣了几个cluster集群测试用,网络用的flannel.因为这些机器都不是纯净的环境(以前部署过其他的k8s或者有一些特别的设置),所以部署起来遇到了很多问题.看了下 ...
- 关于keepalived执行后日志狂刷IPVS: Can't initialize ipvs: Protocol not available的问题
安装了keepalived+lvs,达到了高可用的负载均衡,但是今天再启用的时候发现keepalived不正常,通过 /var/log/messages 查看系统日志发现狂刷 IPVS: Can't ...
- GrapeCity Documents (服务端文档API组件) V3.0 正式发布
近日,葡萄城GrapeCity Documents(服务端文档API组件)V3.0 正式发布! 该版本针对 Excel 文档.PDF 文档和 Word 文档的 API 全面更新,加入了用于生成 Exc ...
- - RabbitMQ - 0 - 介绍、linux 和windows安装
目录 一. 介绍 二.windows安装erlang和rabbitMQ 三.Linux安装erlang和RabbitMQ 一. 介绍 rabbitMQ 是基于 erlang 语言开发的, 为了使用 r ...
- A司入职面试宝典
=公司介绍============================= 世界500强,每股股票2000刀. 面试难度:**** 加班程度:* =面试-流程介绍====================== ...
- 关于JS原型以及原型链、instanceof的一些理解
一.JS原型 首先要区分两个概念 1.构造函数 2.实例:由构造函数通过new方式创建出来的就是实例 <script> function Foo() { } var f = new Foo ...
- k8s之调度器、预选策略及优选函数
1.调度器(scheduler) 调度器的功能是调度Pod在哪个Node上运行,这些调度信息存储在master上的etcd里面,能够和etcd打交道的只有apiserver; kubelet运行在no ...