[BJWC2018]Border 的四种求法
description
luogu
给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\)。
solution
我们考虑转化题面:给定\(l,r\),求满足\(lcs(i,r)\ge i-l+1\)的最大的\(i\)。
对于\(lcs(i,r)\),考虑对\(S\)构建\(SAM\),那么我们知道\(lcs\)的可能取值就是从后缀\(r\)所代表的节点沿着\(fail\)树到根节点的路径上节点的\(len\)的取值。
那么我们得到了一个暴力做法:\(SAM\)+线段树合并后暴跳\(fail\)树,
在每个节点的线段树中查询\([l,min(r,len+l-1))\)中最大的\(i\)。
如何优化?
考虑树链剖分,我们要求出的一条根到父亲的链在\(fail\)树上是若干段重链的前缀。
那么可以对每条重链进行离线而分开考虑。
查询前缀时,除了最后一个点需要查询重儿子及其子树,其余的点都只要查询轻儿子及其子树即可。
因此查询前缀时对于最后一个点仍然使用\(SAM\)+线段树合并的做法,
对于其他的点,考虑将\(lcs(i,r)\ge i-l+1\)变为\(i-len[x]+1\le l\)
只须从上往下一个个插入轻子树的儿子+回答询问。
回答询问时,需要建立一棵以\(i\)为下标,权值为\(i-len[x]+1\)的最小值线段树,
查询时需要查询\(l\le i<r\)且\(i-len[x]+1\le l\)的最大的\(i\),
此时线段树上二分,根据右子树的最小值是否\(\le l\)作出决策,单次时间复杂度降为\(O(logn)\)。
由于每个点到根节点的轻边仅\(O(log)\)条,因此插入的总时间复杂度为\(O(nlog^2n)\)。
线段树处理询问的总时间同样为\(O(nlog^2n)\)。
故总时间复杂度为\(O(nlog^2n)\)。
code
话说这是我第一次写SAM+线段树合并
我错了是我人丑常数大1e5还要跑300ms
#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define FL "a"
using namespace std;
typedef unsigned long long ll;
typedef pair<int,int> PI;
typedef vector<int> VI;
typedef long double dd;
const int N=4e5+10;
const int mod=1e9+7;
const int inf=2147483647;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
}
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
struct node{int l,r,id;};
int n,q;char s[N];vector<node>Q[N];int ans[N];
int head[N],nxt[N],to[N],cnt;
inline void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}
int lst=1,tot=1,cnts=0,len[N],fa[N],tr[N][26],pos[N],fid[N];
inline void extend(int c){
int cur=++tot,u=lst;len[cur]=len[lst]+1;
while(u&&!tr[u][c])tr[u][c]=cur,u=fa[u];
if(!u)fa[cur]=1;
else{
int v=tr[u][c];
if(len[v]==len[u]+1)fa[cur]=v;
else{
int clone=++tot;len[clone]=len[u]+1;
memcpy(tr[clone],tr[v],sizeof(tr[clone]));
fa[clone]=fa[v];fa[v]=fa[cur]=clone;
while(u&&tr[u][c]==v)tr[u][c]=clone,u=fa[u];
}
}
lst=cur;pos[lst]=++cnts;fid[cnts]=lst;
}
namespace s1{
int rt[N],s[2][20*N],tot=0;
void Insert(int &i,int l,int r,int p){
i=++tot;s[0][i]=s[1][i]=0;if(l==r)return;
p<=mid?Insert(s[0][i],l,mid,p):Insert(s[1][i],mid+1,r,p);
}
int Merge(int a,int b){
if(!a||!b)return a|b;int c=++tot;
s[0][c]=Merge(s[0][a],s[0][b]);
s[1][c]=Merge(s[1][a],s[1][b]);
return c;
}
int Query(int i,int l,int r,int x,int y){
if(!i)return 0;if(l==r)return l;
if(mid<y&&s[1][i]){
int res=Query(s[1][i],mid+1,r,x,y);
return res?res:Query(s[0][i],l,mid,x,y);
}
else return x<=mid?Query(s[0][i],l,mid,x,y):0;
}
inline void insert(int u){Insert(rt[u],1,n,pos[u]);}
inline void merge(int u,int v){rt[u]=Merge(rt[u],rt[v]);}
inline int query(int u,int l,int r){return Query(rt[u],1,n,l,r);}
void print(int i,int l,int r){
if(!i)return;if(l==r){printf("%d ",l);return;}
print(s[0][i],l,mid);print(s[1][i],mid+1,r);
}
}
namespace s2{
int mn[N<<2],tot;
void build(int i,int l,int r){
mn[i]=inf;if(l==r)return;build(ls,l,mid);build(rs,mid+1,r);
}
void del(int i,int l,int r,int p){
mn[i]=inf;if(l==r)return;
p<=mid?del(ls,l,mid,p):del(rs,mid+1,r,p);
}
void insert(int i,int l,int r,int p,int v){
if(p<l||p>r)return;mn[i]=min(mn[i],v);if(l==r)return;
p<=mid?insert(ls,l,mid,p,v):insert(rs,mid+1,r,p,v);
}
int query(int i,int l,int r,int x,int y,int v){
if(mn[i]>v)return 0;if(l==r)return mn[i]<=v?l:0;
if(mid<y&&mn[rs]<=v){
int res=query(rs,mid+1,r,x,y,v);
return res?res:query(ls,l,mid,x,y,v);
}
else return x<=mid?query(ls,l,mid,x,y,v):0;
}
}
int sz[N],dep[N],son[N],top[N];
void dfs1(int u){
sz[u]=1;dep[u]=dep[fa[u]]+1;son[u]=0;
for(int i=head[u],v;i;i=nxt[i]){
dfs1(v=to[i]);sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp;if(pos[u])s1::insert(u);
if(son[u])dfs2(son[u],tp),s1::merge(u,son[u]);
for(int i=head[u],v;i;i=nxt[i])
if((v=to[i])!=son[u])dfs2(v,v),s1::merge(u,v);
}
void ins(int u,int len){
if(pos[u])s2::insert(1,1,n,pos[u],pos[u]-len+1);
for(int i=head[u],v;i;i=nxt[i])ins(v=to[i],len);
}
void del(int u){
if(pos[u])s2::del(1,1,n,pos[u]);
for(int i=head[u],v;i;i=nxt[i])del(v=to[i]);
}
inline void getans(int x){
vector<int>a;a.clear();int m=0;
for(;x;x=son[x])a.pb(x),m++;
for(int k=0,u;k<m;k++){
u=a[k];
if(pos[u])s2::insert(1,1,n,pos[u],pos[u]-len[u]+1);
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==son[u])continue;ins(v,len[u]);
}
for(int i=0,sz=Q[u].size(),l,r,id,res;i<sz;i++){
l=Q[u][i].l;r=Q[u][i].r;id=Q[u][i].id;
res=s2::query(1,1,n,l,r-1,l);
if(l<=res&&res<r)ans[id]=max(ans[id],res-l+1);
res=s1::query(u,l,min(len[u]+l-1,r-1));
if(l<=res&&res<r)ans[id]=max(ans[id],res-l+1);
}
}
for(int k=0,u;k<m;k++){
u=a[k];if(pos[u])s2::del(1,1,n,pos[u]);
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==son[u])continue;del(v);
}
}
for(int k=0,u;k<m;k++){
u=a[k];
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==son[u])continue;
getans(v);
}
}
}
inline void init(){
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;i++)extend(s[i]-'a');
for(int i=2;i<=tot;i++)add(fa[i],i);
dfs1(1);dfs2(1,1);s2::build(1,1,n);
}
inline void solve(){
q=read();
for(int i=1,l,r,u;i<=q;i++){
l=read();r=read();u=fid[r];
while(u)Q[u].pb((node){l,r,i}),u=fa[top[u]];
}
getans(1);
for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
}
int main()
{
init();
solve();
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 ...
- 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 ...
- 「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 ...
随机推荐
- springmvc @Valid 接收实体类时出现bean为null的问题
这是因为传到后端之后,全部以全小写形式处理了 所以前端也需要把name设置为全小写,否则后端不识别,导致接收不到,导致为null
- L018-crond的生产场景经验小节
L018-crond的生产场景经验小节 怎么说呢,其实L018这节课还是巩固crond的知识,前半堂课主要是解决上堂课老师留的作业(在L017已经更新,拉到最后),然后剩下的半堂客主要是讲解了一些生产 ...
- OpenGL ES学习笔记(二)——平滑着色、自适应宽高及三维图像生成
首先申明下,本文为笔者学习<OpenGL ES应用开发实践指南(Android卷)>的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载. <Android学习笔记--O ...
- MVC捕获数据保存时的具体字段验证错误代码
////捕获验证错误代码 //try //{ // // 调试写数据库 //} //catch (DbEntityValidationException dbEx) //{ //}
- Qt-QMl-自定义自己想要的TabView
上效果图 上实现源码,这里的代码都是来自Qt官方源码修改其中某一行内容 /* 作者:张建伟 时间:2018年4月8日 简述:自定义TabView,主要实现Tab和实现内容重叠,不在占用独立空间 该文件 ...
- JavaFX学习笔记——ControlsFX控件集学习——ToggleSwitch和BreadCrumbBar例子
ToggleSwitch ToggleSwitch ts = new ToggleSwitch("开"); 效果 BreadCrumbBar BreadCrumbBar<St ...
- Python教程 深入条件控制
while 和 if 条件句中可以使用任意操作,而不仅仅是比较操作. 比较操作符 in 和 not in 校验一个值是否在(或不在)一个序列里.操作符 is 和 is not 比较两个对象是不是同一个 ...
- Qt 链接报错 version `Qt_5' not found
问题: 在Ubuntu 上发布软件包的时候,复制链接库的时候出现Qt 版本找不到的情况: ./libqxcb.so: /usr/lib/x86_64-linux-gnu/libQt5XcbQpa.so ...
- spring-boot 项目整合logback
使用spring-boot项目中添加日志输出,java的日志输出一共有两个大的方案log4j/log4j2 ,logback.log4j2算是对log4j的一个升级版本. 常规做法是引入slf4j作为 ...
- 技本功丨用短平快的方式告诉你:Flink-SQL的扩展实现
2019年1月28日,阿里云宣布开源“计算王牌”实时计算平台Blink回馈给ApacheFlink社区.官方称,计算延迟已经降到毫秒级,也就是你在浏览网页的时候,眨了一下眼睛,淘宝.天猫处理的信息已经 ...