【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 ...
随机推荐
- uva 1400 "Ray, Pass me the dishes!" (区间合并 最大子段和+输出左右边界)
题目链接:https://vjudge.net/problem/UVA-1400 题意:给一串序列,求最大子段,如果有多个,输出字典序最小的那个的左右端点 思路: 之前写过类似的,这个麻烦点需要输出左 ...
- 数据结构:BF算法
贴上源代码: #include<iostream> using namespace std; int BF(char S[],char T[]) { int i,j; i = j = 0; ...
- 用shell脚本安装MySQL-5.7.22-官方版本
Install_CentOS7_MySQL57_binary.sh #!/bin/bash MySQL_Package=mysql-5.7.22-linux-glibc2.12-x86_64.tar. ...
- MyBatis学习存档(4)——进行CRUD操作
使用MyBatis进行数据库的CRUD操作有2种方式:一种如之前所说的接口+xml,而另一种是通过对接口上的方法加注解(@Select @Insert @Delete @Update) 但是通常情况下 ...
- k8s-helm安装
kubernetes 1.15安装部署helm插件 简单介绍: Helm其实就是一个基于Kubernetes的程序包(资源包)管理器,它将一个应用的相关资源组织成为Charts,并通过Charts ...
- 排序之快排(JS)
快速排序(Quicksort)是对冒泡排序的一种改进. 它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分 ...
- hdu 6047
题解:先对b排序,用一个数组预处理a,记录当前位置之后到n的最大值,然后在用一个变量维护新增变量的最大值,用的时候和前面的数组的最大值做一个比较就ok. AC代码: #include <cstd ...
- OSG3.4内置Examples解析【目录】
opengl渲染管线 从整体上解读OpenGL的渲染流程 一 从整体上解读OpenGL的渲染流程 二 osg与animate相关示例解析 OSG3.4内置Examples(osganimate)解析 ...
- [转载]为什么jar包中能看见源码
[转载]为什么jar包中能看见源码 这个也是我之前发现过的一个现象,只是之前没有研究过.今天正好在知乎看见,总结一下: 对于Maven或者Gradle项目,依赖的部分会自动从远程仓库下载源码 生成的j ...
- js重点——作用域——作用域分类(三)
一.作用域可以分为全局作用域,局部作用域(函数作用域)和块级作用域. 1.全局作用域 代码在程序中的任何位置都能被访问到,window对象的内置属性都拥有全局作用域. <script> v ...