[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)
题目描述
给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border 。
Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度。
题解
这题的描述很短,给人一种很可做的假象。
暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq。
暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在parent树上跳father,相当于枚举LCA,那么如果有匹配的点,则一定满足:
i-len[LCA]+1<=l => i=len[LCA]<l => i<l+len[LCA] i>=l&&i<r (i为我们要求的点,l为询问的左端点,LCA为i和r的LCA)
所以我们只需要在线段树上查子树内查询满足上述条件的最大的i就可以了,复杂度最好qlogn,最差qn。
这两种暴力好像差不多。。
我们观察到第二种暴力它的瓶颈在于枚举LCA,但查询只需要一个log,我们可以想一些办法把复杂度均摊一下。
链分治
这就是这道题的重头戏,它用到了一个重要的性质,我们将一棵树重链剖分之后,从根到任意一点的路径上,轻重链切换的次数是不超过log的。
然后我们就可以用它搞一些事情。
比如说这道题,我们可以把询问拆成log个挂在它到根的路径上,每条链挂一个,对于当前链,挂在原来的点上,对于上面的链,挂在链的最底端。
其实我们也是相当于在枚举lca,对于挂上去的点,我们可以直接用上面暴力2的方法统计答案,复杂度是log的,那么对于其他点,我们如何统计答案呢?
我们可以发现一个性质,就是对于最底端这个点向上的其他链上的点作为LCA时,答案只会出现在这个点除了重儿子以外的子树内以及自己,因为重链底下的子树我们刚才已经处理过了。
所以我们的做法就是,对于每一条重链,自顶至底处理,然后我们把式子移个项
i-len[LCA]<l
我们把这个点的所有非重儿子所在子树中的点的i-len[LCA]全部加进以i为下标的线段树中,然后一直往下合并,那么每到一个点,它就保存了它到重链顶端所有LCA的信息,然后我们直接询问挂在这个点的询问就可以了。
但每次暴力加的复杂度对吗?
树上启发式合并(DSU on tree)
这个操作的原理和上面的一模一样,都是利用了轻重链log的性质。
主要思想就是,维护重链,轻链暴力,考虑一个点会被暴力做多少次,它到根的路径上轻重链切换的时候才会产生一次,根据上面的结论,这是log的。
于是我们在nlog2的时间内做完了这道题。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 400002
#define inf 2e9
using namespace std;
vector<int>vec[N];
char s[N];
int last,cnt,n,tot,head[N],topp,ls[N*],rs[N*],tr[N*],T[N<<],len[N],id[N],ch[N][],fa[N],f[N];
int L[N*],R[N*],size[N],top[N],son[N],ans[N],q,rt[N],_id[N],mi[N*];
inline int rd(){
int x=;char c=getchar();bool f=;
while(!isdigit(c)){if(c=='-')f=;c=getchar();}
while(isdigit(c)){x=(x<<)+(x<<)+(c^);c=getchar();}
return f?-x:x;
}
struct edge{int n,to;}e[N<<];
struct node{int l,r;}qu[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
inline int newnode(){
int x=++topp;tr[x]=inf;ls[x]=rs[x]=;
return x;
}
void ins(int &cnt,int l,int r,int x){
if(!cnt)cnt=++topp,mi[cnt]=inf;mi[cnt]=min(mi[cnt],x);
if(l==r)return;
int mid=(l+r)>>;
if(mid>=x)ins(L[cnt],l,mid,x);
else ins(R[cnt],mid+,r,x);
}
int merge(int cnt,int pre,int l,int r){
if(!cnt||!pre)return cnt^pre;
int p=++topp;mi[p]=min(mi[cnt],mi[pre]);
if(l==r)return p;
int mid=(l+r)>>;
L[p]=merge(L[cnt],L[pre],l,mid);
R[p]=merge(R[cnt],R[pre],mid+,r);
return p;
}
int prequery(int cnt,int l,int r,int ql,int qr,int x){
if(mi[cnt]>=x)return ;
if(l==r)return (l>=ql&&l<=qr)?l:;
int mid=(l+r)>>;
if(mid<qr&&mi[R[cnt]]<x){
int num=prequery(R[cnt],mid+,r,ql,qr,x);
if(num)return num;
}
else if(mid>=ql&&mi[L[cnt]]<x)return prequery(L[cnt],l,mid,ql,qr,x);
return ;
}
inline void insert(int x,int tag){
int p=last,np=++cnt;last=np;len[np]=len[p]+;
ins(T[np],,n,tag);id[tag]=np;_id[np]=tag;
for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;
if(!p)fa[np]=;
else{
int q=ch[p][x];
if(len[p]+==len[q])fa[np]=q;
else{
int nq=++cnt;len[nq]=len[p]+;
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
}
}
}
void ins2(int &cnt,int l,int r,int x,int y){
if(!cnt)cnt=newnode();tr[cnt]=min(tr[cnt],y);
if(l==r)return;
int mid=(l+r)>>;
if(mid>=x)ins2(ls[cnt],l,mid,x,y);
else ins2(rs[cnt],mid+,r,x,y);
}
int merge2(int cnt,int pre,int l,int r){
if(!cnt||!pre)return cnt^pre;
int mid=(l+r)>>;tr[cnt]=min(tr[cnt],tr[pre]);
if(l==r)return cnt;
ls[cnt]=merge2(ls[cnt],ls[pre],l,mid);
rs[cnt]=merge2(rs[cnt],rs[pre],mid+,r);
return cnt;
}
int query(int cnt,int l,int r,int L,int R,int x){ ///!!!
if(l==r)return (l>=L&&l<=R)?l:;
if(tr[cnt]>=x)return ;
int mid=(l+r)>>;
if(mid<R&&tr[rs[cnt]]<x){
int num=query(rs[cnt],mid+,r,L,R,x);
if(num)return num;
}
if(tr[ls[cnt]]<x&&mid>=L)return query(ls[cnt],l,mid,L,R,x);
return ;
}
void dfs1(int u){
size[u]=;
for(int i=head[u];i;i=e[i].n){
int v=e[i].to;
f[v]=u;dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]])son[u]=v;
}
}
void dfs2(int u){
if(!top[u])top[u]=u;if(son[u])top[son[u]]=top[u],dfs2(son[u]);
for(int i=head[u];i;i=e[i].n){
if(e[i].to!=son[u])dfs2(e[i].to);
T[u]=merge(T[u],T[e[i].to],,n);
}
}
void prework(int cnt,int l,int r,int top){
if(l==r){ins2(rt[top],,n,l,l-len[top]);return;}
int mid=(l+r)>>;
if(L[cnt])prework(L[cnt],l,mid,top);
if(R[cnt])prework(R[cnt],mid+,r,top);
}
void solve(int u){
for(int i=head[u];i;i=e[i].n)solve(e[i].to);
if(top[u]==u){
int x=u;
while(x){
for(int i=head[x];i;i=e[i].n){
int v=e[i].to;if(v==son[x])continue;
prework(T[v],,n,x);
}
for(int i=;i<vec[x].size();++i){
int l=qu[vec[x][i]].l,r=qu[vec[x][i]].r;
ans[vec[x][i]]=max(ans[vec[x][i]],query(rt[x],,n,l,r-,l));
}
if(son[x])rt[son[x]]=merge2(rt[son[x]],rt[x],,n);
x=son[x];
}
}
}
int main(){
tr[]=mi[]=inf;
scanf("%s",s+);n=strlen(s+);
cnt=last=;
for(int i=;i<=n;++i)insert(s[i]-'a',i);
for(int i=;i<=cnt;++i)add(fa[i],i);
dfs1();dfs2();
q=rd();topp=;//合并完后的第一类线段树不会新增节点了,所以把它清空。
for(int i=;i<=q;++i){
qu[i].l=rd();qu[i].r=rd();int x=id[qu[i].r];
while(x){
vec[x].push_back(i);
ans[i]=max(ans[i],prequery(T[x],,n,qu[i].l,qu[i].r-,qu[i].l+len[x]));//!!!
x=f[top[x]];
}
}
for(int i=;i<=n;++i)ins2(rt[id[i]],,n,i,);
solve();
for(int i=;i<=q;++i)printf("%d\n",ans[i]?ans[i]-qu[i].l+:);
return ;
}
[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)的更多相关文章
- luogu P4482 [BJWC2018] Border 的四种求法 - 后缀数组
题目传送门 传送门 题目大意 区间border. 照着金策讲稿做. Code /** * luogu * Problem#P4482 * Accepted * Time: 8264ms * Memor ...
- 【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并
题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r ...
- [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.. ...
- CF547E Milk and Friends(AC自动机的fail指针上建主席树 或 广义后缀自动机的parent线段树合并)
What-The-Fatherland is a strange country! All phone numbers there are strings consisting of lowercas ...
- CF1037H Security 后缀自动机 + right集合线段树合并 + 贪心
题目描述: 给定一个字符串 $S$ 给出 $Q$ 个操作,给出 $L,R,T$,求出字典序最小的 $S_{1}$ 为 $S[L...R]$的子串,且 $S_{1}$ 的字典序严格大于 $T$. 输出这 ...
- 【LuoguP4482】[BJWC2018]Border 的四种求法
题目链接 题意 区间 boder \(n,q\leq 2*10^5\) Sol (暴力哈希/SA可以水过) 字符串区间询问问题,考虑用 \(SAM\) 解决. boder相当于是询问区间 \([l,r ...
- CF700E Cool Slogans 后缀自动机 + right集合线段树合并 + 树形DP
题目描述 给出一个长度为n的字符串s[1],由小写字母组成.定义一个字符串序列s[1....k],满足性质:s[i]在s[i-1] (i>=2)中出现至少两次(位置可重叠),问最大的k是多少,使 ...
随机推荐
- react双组件传值和传参
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Chrome浏览器的版本查看 以及V8 javascript 引擎版本查看
1. 发现chrome浏览器最新版本里面带的V8 引擎 版本号与chrome的版本号有一个关系, 这里简单总结一下: 在地址栏里面输入: chrome://version 即可显示出来 比如我正在使用 ...
- 1244. Minimum Genetic Mutation
描述 A gene string can be represented by an 8-character long string, with choices from "A", ...
- eclipse打开package explorer视图
第一步:window-show view-other 第二步:
- 基于vue-cli,sass,vant的移动端项目
项目架构 开始 vue init webpack 项目名称 //新建项目,cd进入新项目 npm install axios //先安装! ...
- Jenkins整合SonarQube代码检测工具
借鉴博客:https://blog.csdn.net/kefengwang/article/details/54377055 上面这博客写得挺详细的,挺不错.它这个博客没有提供下载的教程,这个博客提供 ...
- windows浏览器访问虚拟机开的rabbitmq服务,无法访问
根据这个博主的建议 https://blog.csdn.net/csdnliuxin123524/article/details/78207427 换了一个浏览器上火狐浏览器输入“localhost: ...
- AJAX+springmvc遇到的问题
当我使用AJAX将表单的值传入处理器中后,经过了一个判断再进行页面跳转时,不能在处理器中使用重定向,它会将重定向的页面内容在AJAX的data中显示出来而不是显示一个页面 所以只能在AJAX 的suc ...
- linux audit审计(8)--ausearch搜索audit日志文件
ausearch这个工具,可以针对指定的事件来搜索audit日志文件.默认情况下,ausearch搜索/var/log/audit/audit.log这个文件. The ausearch utilit ...
- QTP 自动化测试桌面程序--笔记(关闭 启动程序脚本) 、安装
0 安装qtp .exe 文件 安装 插件文件(如delph) 1 关闭 启动程序: 将要操作的程序-存入localdatatable中 设置 迭代一次 rem SystemUtil.ClosePro ...