原文链接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);
else
Ins(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){// <xR
if (!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;
else
return 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);
else
Ins(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;
}

  

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

  1. 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)

    这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...

  2. luogu P4482 [BJWC2018]Border 的四种求法

    luogu 对于每个询问从大到小枚举长度,哈希判断是否合法,AC 假的(指数据) 考虑发掘border的限制条件,如果一个border的前缀部分的末尾位置位置\(x(l\le x < r)\)满 ...

  3. luogu P4482 [BJWC2018] Border 的四种求法 - 后缀数组

    题目传送门 传送门 题目大意 区间border. 照着金策讲稿做. Code /** * luogu * Problem#P4482 * Accepted * Time: 8264ms * Memor ...

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

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

  5. [BJWC2018]Border 的四种求法

    description luogu 给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\). solution 我们考虑转化题面:给定\ ...

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

    题目链接 题意 区间 boder \(n,q\leq 2*10^5\) Sol (暴力哈希/SA可以水过) 字符串区间询问问题,考虑用 \(SAM\) 解决. boder相当于是询问区间 \([l,r ...

  7. 「BJWC2018」Border 的四种求法

    「BJWC2018」Border 的四种求法 题目描述 给一个小写字母字符串 \(S\) ,\(q\) 次询问每次给出 \(l,r\) ,求 \(s[l..r]\) 的 Border . \(1 \l ...

  8. 洛谷 P4478 [BJWC2018]上学路线

    洛谷 P4478 [BJWC2018]上学路线 原题 神仙题orz,竟然没有1A....容斥+卢卡斯+crt?? 首先用容斥做,记\(f[i][0/1]\)表示到i号点经过了奇数/偶数个点的方案数,因 ...

  9. 洛谷 P4484 - [BJWC2018]最长上升子序列(状压 dp+打表)

    洛谷题面传送门 首先看到 LIS 我们可以想到它的 \(\infty\) 种求法(bushi),但是对于此题而言,既然题目出这样一个数据范围,硬要暴搜过去也不太现实,因此我们需想到用某种奇奇怪怪的方式 ...

随机推荐

  1. 【LOJ6060】【2017 山东一轮集训 Day1 / SDWC2018 Day1】Set 线性基

    题目大意 给出 \(n\) 个非负整数,将数划分成两个集合,记为一号集合和二号集合.\(x_1\) 为一号集合中所有数的异或和,\(x_2\) 为二号集合中所有数的异或和.在最大化 \(x_1 + x ...

  2. postgreSQL学习(二):pgsql的一些基础操作

    在上一篇文章中我们学习了怎么安装pgsql,安装好了后,我们来学习一下怎么对pgsql进行创建操作以及相关的crud的操作啦 一 创建数据库 $ createdb test 然后你可能会遇到如下的错误 ...

  3. java 11 增加了一系列的字符串处理方法,Optional 加强 ,改进的文件API

    增加了一系列的字符串处理方法 如以下所示. // 判断字符串是否为空白 " ".isBlank(); // true // 去除首尾空白 " Javastack &quo ...

  4. Windows 环境下的 protoc 安装(转)

    如果是为了编译hadoop2.8.0源码,必须使用2.5.0版本的protobuf,安装方法同下 1. 下载需要的安装包:https://github.com/google/protobuf/rele ...

  5. SQL Server数据库的备份和还

    转:http://blog.csdn.net/zwj7612356/article/details/8188025 在sql server数据库中,备份和还原都只能在服务器上进行,备份的数据文件在服务 ...

  6. JS常用基础知识

    前言:在js中dom和bom是我们操作的基本,在最初接触时候我也懵,但是后来慢慢发现其实bom就是操作浏览器,而dom就是操作文本框节点.

  7. OpenStack VS Kubernetes,谁是你心中的王者?

      当下云计算的领域里热度最高的两个项目,无疑是OpenStack和Kubernetes.如果云计算是一个风起云涌的江湖,毫不夸张的说OpenStack和Kubernetes就是江湖里的泰山北斗.Op ...

  8. oldboy s21day07(深浅拷贝及文件操作)

    #!/usr/bin/env python# -*- coding:utf-8 -*- # 1.看代码写结果'''v1 = [1, 2, 3, 4, 5]v2 = [v1, v1, v1]v1.app ...

  9. JAVA传递带有空格的参数

    String s="b2 + b1"; Process child = Runtime.getRuntime().exec("C:\\eclipse-workspace\ ...

  10. Resources (being shared)

    论文下载求助论坛 数学杂志的模版 答辩PPT模版 发一篇文章的经历 数学期刊名称缩写 英文书籍下载 英文书籍下载 中文书籍下载 数学分析高等代数考研试题官方下载地址