洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接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的更多相关文章
- 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)
这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...
- luogu P4482 [BJWC2018]Border 的四种求法
luogu 对于每个询问从大到小枚举长度,哈希判断是否合法,AC 假的(指数据) 考虑发掘border的限制条件,如果一个border的前缀部分的末尾位置位置\(x(l\le x < r)\)满 ...
- luogu P4482 [BJWC2018] Border 的四种求法 - 后缀数组
题目传送门 传送门 题目大意 区间border. 照着金策讲稿做. Code /** * luogu * Problem#P4482 * Accepted * Time: 8264ms * Memor ...
- [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)
题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...
- [BJWC2018]Border 的四种求法
description luogu 给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\). solution 我们考虑转化题面:给定\ ...
- 【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 ...
- 洛谷 P4478 [BJWC2018]上学路线
洛谷 P4478 [BJWC2018]上学路线 原题 神仙题orz,竟然没有1A....容斥+卢卡斯+crt?? 首先用容斥做,记\(f[i][0/1]\)表示到i号点经过了奇数/偶数个点的方案数,因 ...
- 洛谷 P4484 - [BJWC2018]最长上升子序列(状压 dp+打表)
洛谷题面传送门 首先看到 LIS 我们可以想到它的 \(\infty\) 种求法(bushi),但是对于此题而言,既然题目出这样一个数据范围,硬要暴搜过去也不太现实,因此我们需想到用某种奇奇怪怪的方式 ...
随机推荐
- 基于maven的spring-boot的pom文件详解
Spring Boot 推荐的基础 POM 文件 名称 说明 spring-boot-starter 核心 POM,包含自动配置支持.日志库和对 YAML 配置文件的支持. spring-boot-s ...
- shell之获取终端信息
#!/bin/bash #tput和stty是两款终端处理工具 #获取列数和行数 tput cols tput lines #打印当前终端名 tput longname #移动光标 移动光标到100 ...
- docker_weave
安装 curl -L git.io/weave -o /usr/local/bin/weave chmod a+x /usr/local/bin/weave 启动 weave weave launch ...
- 洛谷 P2042 【[NOI2005]维护数列】
一直在想要做这道题,但是被那个硕大的Splay标签压垮了 好了,切入正题 这道题应该是我第二次用splay来维护区间问题 我还是太菜了QAQ 其实思路也很简单,就是以每一个位置的下标来进行维护,然后其 ...
- nginx安装访问
依赖包安装: 安装gcc gcc-c++: yum -y install gcc gcc-c++ autoconf automake 安装pcre: yum -y install pcre pcre- ...
- Redux Todos Example
此项目模板是使用Create React App构建的,它提供了一种简单的方法来启动React项目而无需构建配置. 使用Create-React-App构建的项目包括对ES6语法的支持,以及几种非官方 ...
- Java使用DOM4J对XML文件进行增删改查操作
Java进行XML文件操作,代码如下: package com.founder.mrp.util; import java.io.File; import java.util.ArrayList; i ...
- html设置背景图片并自适应
<style> html{ height:100%; } body{ padding: 0; margin: 0; background: url(images/2.jpg); backg ...
- 设 $y_1(x), y_2(x)$ 是 $y''+p(x)y'+q(x)y=0$ 的两个解 ($p(x), q(x)$ 连续), 且 $y_1(x_0)=y_2(x_0)=0$, $y_1(x)\not\equiv 0$. 试证: $y_1(x)$, $y_2(x)$ 线性相关.
设 $y_1(x), y_2(x)$ 是 $y''+p(x)y'+q(x)y=0$ 的两个解 ($p(x), q(x)$ 连续), 且 $y_1(x_0)=y_2(x_0)=0$, $y_1(x)\n ...
- sql注入学习 sqlliab教程 lesson1 (sqlliab搭建教程)
靶场搭建 小白建议直接用集成环境.推荐laragon (由于这套靶场较早,需要使用php7.0以下环境,安装完php laragon需要在安装php低版本,默认laragon只集成了一个7.0的php ...