原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html

题意

给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L...R] 的最长前后缀。

$$q,|S|\leq 2 \times 10 ^ 5$$

题解

真是一道有趣的字符串题。

首先我们给 S 建出 SAM ,并用线段树合并预处理出每一个节点的 Right 集合。

我们要做的是找到最大的 $p$ 满足 $p<R, S[L...p] = S[R-p+L...R]$ ,即 $p<R, p - LCS(S[1...p],S[1...R]) + 1 \leq L$ 。($p - LCS(S[1...p],S[1...R]) + 1$ 相当于算出 $LCS(S[1...p],S[1...R])$ 的左端点位置。)

由于两个前缀的 LCS 就是他们在 parent 树上的 LCA 的 Max,所以我们来用parent树上的节点来表示这个式子的意义:设 S[1...x] 在 parent 树上对应的节点为 pos(x) ,则我们就是要找到这样一个 $p$ ,它满足 $p<R,p-Max(LCA(pos(p),pos[R]))+1\leq L$ 。

上式等价于 $p<\min(R,L+Max(LCA(pos(p),pos(R)))$ 。有 min 很棘手,我们先把他干掉:考虑到 pos(R) 的祖先的 Max 是随着深度变小而递减的,那么如果我们从 pos(R) 开始,一步一步跳 father ,则一定会经过一个临界点 $x$ ,满足:在这个点以及它之前跳到的任意一点 $a$,满足 $R\leq L+Max(pos(a))$;在这个点之后跳到的任意一点 $b$ ,满足 $R\geq L+Max(pos(a))$ 。对于点 $x$ 以及之前的点,我们只需要在点 $x$ 的 Right 集合中找到 $<R$ 的最大的值就好了,这个东西线段树上二分就可以了。

现在考虑剩下一半,这一半只需要满足:

$$p-Max(LCA(pos(p),pos[R]))+1\leq L$$

我们来看看询问的时候需要干什么:询问先处理掉临界点以下的,直接从临界点开始跳。对于跳到的每一个节点,我们要干什么呢?设当前节点为 c ,设它为 LCA ,那么我们只要在它的 Right 集合中找到 $<L+Max(c)$ 的最大值就好了,同样还是线段树上二分。如果设 s 为 c 的一个儿子,它的子树中包含了 pos(R) ,你会发现我多算了这个子树的贡献。但是稍加思索就可以得知,它的贡献会在子树中再算一遍,而在子树中的 Max 比较大,所以再算一次不会更优。

我们考虑对 parent 树进行树链剖分。

现在我们已经给他树链剖分了,所以我们可以把临界点到根路径划分成 $O(\log n)$ 段重链,而且每一段都是某条重链的前缀。

考虑把询问进行离线,对于每一条重链按照深度顺序从浅到深加入当前节点的贡献(用线段树维护),即当前集合的 Right 集合内的所有元素 减去当前节点的 Max 然后+1,在加入的同时回答询问。这样做的复杂度显然是不对的!但是我们只要改一改就对了。对于一个节点 d 的重儿子的 Right 集合,d 为 LCA 时一定不比 重儿子为 LCA 时优,所以我们不加重儿子的 Right 集合的贡献,留到重儿子里面加。这样的话,每次询问的时候,询问的前缀重链的最深点要特殊处理。为什么这样时间复杂度是对的?这个东西其实就是 DSU on Tree 的复杂度,具体来说每一个点的对时间复杂度的贡献次数是它到根路径上轻重链切换的次数。

于是我们得到了一个 $O((|S|+q)\log^2 |S|)$ 的做法。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
#define For(i,a,b) for (int i=a;i<=b;i++)
#define Fod(i,b,a) for (int i=b;i>=a;i--)
using namespace std;
typedef long long LL;
LL read(){LL x=0,f=0;char ch=getchar();while (!isdigit(ch))f|=ch=='-',ch=getchar();while (isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();return f?-x:x;
}
const int N=200005*2;
int n,Q;
int pnode[N];
char s[N];
vector <int> id;
namespace Mseg{const int S=N*25;int ls[S],rs[S],size;void Init(){size=0,clr(ls),clr(rs);}void Ins(int &rt,int L,int R,int x){if (!rt)rt=++size;if (L==R)return;int mid=(L+R)>>1;if (x<=mid)Ins(ls[rt],L,mid,x);elseIns(rs[rt],mid+1,R,x);}int Merge(int a,int b,int L,int R){if (!a||!b)return a+b;int rt=++size;if (L<R){int mid=(L+R)>>1;ls[rt]=Merge(ls[a],ls[b],L,mid);rs[rt]=Merge(rs[a],rs[b],mid+1,R);}return rt;}void GetR(int rt,int L,int R,vector <int> &v){if (!rt)return;if (L==R)return (void)v.push_back(L);int mid=(L+R)>>1;GetR(ls[rt],L,mid,v);GetR(rs[rt],mid+1,R,v);}int Getpre(int rt,int L,int R,int xR){// <xRif (!rt||L>=xR)return 0;if (L==R)return L;int mid=(L+R)>>1,v=Getpre(rs[rt],mid+1,R,xR);if (v)return v;elsereturn Getpre(ls[rt],L,mid,xR);}
}
namespace SAM{struct Node{int Next[26],fa,Max,pos;}t[N];int last,root,size;int rt[N],id[N];void Init(){clr(t),Mseg::Init();last=root=size=1;}void extend(int c,int ps){int p=last,np=++size,q,nq;t[np].Max=t[p].Max+1,t[np].pos=ps,pnode[ps]=np;Mseg::Ins(rt[np],1,n,ps);for (;p&&!t[p].Next[c];p=t[p].fa)t[p].Next[c]=np;if (!p)t[np].fa=1;else {q=t[p].Next[c];if (t[p].Max+1==t[q].Max)t[np].fa=q;else {nq=++size;t[nq]=t[q],t[nq].Max=t[p].Max+1,t[nq].pos=ps;t[np].fa=t[q].fa=nq;for (;p&&t[p].Next[c]==q;p=t[p].fa)t[p].Next[c]=nq;}}last=np;}void Sort(){static int tax[N];clr(tax);For(i,1,size)tax[t[i].Max]++;For(i,1,size)tax[i]+=tax[i-1];For(i,1,size)id[tax[t[i].Max]--]=i;}void build(){Sort();Fod(i,size,2){int x=id[i],f=t[x].fa;rt[f]=Mseg::Merge(rt[f],rt[x],1,n);}}
}
using SAM::t;
vector <int> e[N],qs[N];
int fa[N][20],son[N],size[N],top[N],aI[N],Time;
void dfs(int x){fa[x][0]=t[x].fa,size[x]=1,son[x]=0;For(i,1,19)fa[x][i]=fa[fa[x][i-1]][i-1];for (auto y : e[x]){dfs(y);size[x]+=size[y];if (!son[x]||size[y]>size[son[x]])son[x]=y;}
}
void Get_Top(int x,int Top){top[x]=Top,aI[++Time]=x;if (son[x])Get_Top(son[x],Top);for (auto y : e[x])if (y!=son[x])Get_Top(y,y);
}
struct que{int L,R,ans;que(){}que(int _L,int _R){L=_L,R=_R;}
}q[N];
void ins_q(int L,int R,int id){int x=pnode[R];Fod(i,19,0)if (L+t[fa[x][i]].Max-1>=R)x=fa[x][i];q[id].ans=max(q[id].ans,Mseg::Getpre(SAM::rt[x],1,n,R));x=fa[x][0];while (x){q[id].ans=max(q[id].ans,Mseg::Getpre(SAM::rt[x],1,n,L+t[x].Max));assert(L+t[x].Max<=R);qs[x].push_back(id),x=fa[top[x]][0];}
}
namespace Seg{const int S=N*2*4;int ls[N],rs[N],Max[N],size;void Init(){while (size)ls[size]=rs[size]=Max[size]=0,size--;}void Ins(int &rt,int L,int R,int x,int v){if (!rt)rt=++size;Max[rt]=max(Max[rt],v);if (L==R)return;int mid=(L+R)>>1;if (x<=mid)Ins(ls[rt],L,mid,x,v);elseIns(rs[rt],mid+1,R,x,v);}int Query(int rt,int L,int R,int xL,int xR){if (!rt||L>xR||R<xL)return 0;if (xL<=L&&R<=xR)return Max[rt];int mid=(L+R)>>1;return max(Query(ls[rt],L,mid,xL,xR) ,Query(rs[rt],mid+1,R,xL,xR));}
}
void solve(){int sz=SAM::size,root;For(i,1,sz){if (i==1||!son[aI[i-1]])Seg::Init(),root=0;int x=aI[i];for (auto j : qs[x])q[j].ans=max(q[j].ans,Seg::Query(root,1,n,1,q[j].L));id.clear(),id.push_back(t[x].pos);for (auto y : e[x])if (y!=son[x])Mseg::GetR(SAM::rt[y],1,n,id);for (auto y : id)Seg::Ins(root,1,n,y-t[x].Max+1,y);}
}
int main(){scanf("%s",s+1),n=strlen(s+1);SAM::Init();For(i,1,n)SAM::extend(s[i]-'a',i);SAM::build();For(i,2,SAM::size)e[t[i].fa].push_back(i);dfs(1),Time=0,Get_Top(1,1),Q=read();For(i,1,Q){q[i].L=read(),q[i].R=read(),q[i].ans=0;ins_q(q[i].L,q[i].R,i);}solve();For(i,1,Q)printf("%d\n",max(0,q[i].ans-q[i].L+1));return 0;
}

  

转载于:https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html

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

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

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

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

    题目链接 题意 区间 boder n,q≤2∗105n,q\leq 2*10^5n,q≤2∗105 Sol (暴力哈希/SA可以水过) 字符串区间询问问题,考虑用 SAMSAMSAM 解决. bode ...

  3. 洛谷 P4471 [BJWC2018]词韵 (字典树)

    题目链接:https://www.luogu.org/problemnew/show/P4471 题目描述 Adrian 很喜欢诗歌中的韵.他认为,两个单词押韵当且仅当它们的最长公共后缀的长度至少是其 ...

  4. 【笔记|C++】最大公约数、最小公倍数的四种求法

    前言 Hello!小伙伴! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出-   自我介绍 ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计 ...

  5. 洛谷P5357 - 【模板】AC自动机(二次加强版)(AC自动机+fail树)

    题目链接:点击查看 题目大意:给出n个模式串,问在主串中分别出现了多少次 题目分析:如果像以往那样,在匹配的时候fail指针乱跳的话,那么是错误的AC自动机使用方法,时间复杂度也大大上升,接近于暴力的 ...

  6. 洛谷 P1914 小书童——凯撒密码 C/C++ 字符串

    不需要开数组 边读边处理 //P1914 小书童--凯撒密码 #define LOCAL #include <iostream> #include <cstdio> #incl ...

  7. 组合数C(m,n)的四种求法

    文章目录 递推.杨辉三角 乘法逆元.费马小定理 Lucas定理 欧拉筛.阶乘的质因子.高精度 递推.杨辉三角 AcW885. 求组合数 I #include<bits/stdc++.h> ...

  8. 洛谷or牛客数据结构+算法

    栈思想:先进后出 tips:栈里能放下标就放下标 (牛客)小c的计事本(直接用stack可以简化代码,且不会被自己绕晕,当时没意识到) (牛客)吐泡泡(没意识到用栈),(牛客)好串 1.后缀表达式(栈 ...

  9. 洛谷 P1149 火柴棒等式(太suang络吧)

    题目描述 给你n根火柴棍,你可以拼出多少个形如"A+B=CA+B=C"的等式?等式中的AA.BB.CC是用火柴棍拼出的整数(若该数非零,则最高位不能是00).用火柴棍拼数字0-90 ...

最新文章

  1. java selectcommand_“对于不返回任何基表信息的 SelectCommand 不支持动态SQL生成”-奇怪的错误,不知道原因! | 学步园...
  2. 类的成员函数指针和mem_fun适配器的用法
  3. Spring整合Hibernate 二 - 声明式的事务管理
  4. hoj 13788 Dwarves
  5. 【吐槽】博客园新的原创文章在搜索引擎的排名不及转载的站点
  6. Java中的访问者设计模式–示例教程
  7. UIScollView Touch事件
  8. python不会英语不会数学怎么自学-数学不好、英语不好、非本专业,想学Python数据分析,能安排吗?...
  9. c语言自动化课题设计,自动化专业C语言程序设计课堂教学方案设计和实践.doc
  10. 配置Gitlab Push自动触发jenkins构建
  11. 内核操作系统Linux内核变迁杂谈——感知市场的力量
  12. D3D游戏关于窗口中如何精确确定鼠标位置的相关讨论
  13. 【元胞自动机】基于matlab元胞自动机考虑驾驶行为的自动—求解手动驾驶混合交通流问题【含Matlab源码 2060期】
  14. 手机上编程python的软件_盘点几个在手机上可以用来学习编程的软件
  15. cloud2声卡_带你解惑HyperX Cloud2(飓风)和Alpha(阿尔法)的终极选择
  16. 我的世界java1.15.2光影_我的世界Java版带光影
  17. Andriod Studio 安装过程
  18. 分段函数的期望和方差_导数排列组合期望方差.doc
  19. Authentication and Authorization授权与验证
  20. javac编译错误: 程序包 com.sun.xxx 不存在

热门文章

  1. java nio 多路复用_8分钟深入浅出搞懂BIO、NIO、AIO
  2. shell编程关于数组的那点事
  3. 如果深入学习前端,大佬给你总结了几个技巧!
  4. 10个实用的 ES6 方法
  5. 10个JavaScript图像处理库,收藏好留备用
  6. QString::number()相关转换
  7. python井字棋如何判断输赢_井字棋判断输赢的两种方法
  8. c if标签怎么用android,android – 使用NDK将YUV解码为C/C++中的RGB
  9. img 样式单和属性
  10. linux内核字符驱动设备,Linux学习笔记——linux内核字符设备驱动-Go语言中文社区...