洛谷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),但是对于此题而言,既然题目出这样一个数据范围,硬要暴搜过去也不太现实,因此我们需想到用某种奇奇怪怪的方式 ...
随机推荐
- bootstrapValidator操作
1.html中表单初始化 <script> //表单验证初始化 $('#wx_pay_account_form_id').bootstrapValidator({ message : 'T ...
- magento2 - Invalid credentials for 'https://repo.magento.com/packages.json', aborting.
错误如下: 登陆:https://developer.magento.com/找到路径-创建公钥与私钥: Developer Portal -> My Access Keys -> Cre ...
- 「CF1154F」Shovels Shop【背包DP】
题目链接 [洛谷传送门] 题解 非常简单的背包. \(f[i]\)表示购买\(i\)个物品所需要最少的花费. 不考虑免费的限制条件,那么一定是选择前\(k\)个双鞋子. 那么加入免费的条件,那么还是要 ...
- 深入理解ES6箭头函数中的this
简要介绍:箭头函数中的this,指向与一般function定义的函数不同,比较容易绕晕,箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定. 1.何为定义时 ...
- nginx服务器的基本配置
nginx作为反向代理搭建服务器的优点. 处理响应请求很快:单次请求会得到更快的响应.在高峰期,Nginx 可以比其它的 Web 服务器更快的响应请求 高并发连接:理论上,Nginx 支持的并发连接上 ...
- Python读写文件的几种方式
一.pandas pandas模块是数据分析的大杀器,它使得对于文件相关的操作变得简单. 看一下它的简单使用 import pandas as pd # 读取 df = pd.read_csv('al ...
- 09--STL关联容器(map/multimap)
一:map/multimap的简介 map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对.它提供基于key的快速检索能力. map中key值是唯一的.集合中的元素按一定的顺 ...
- MongoDB 3.6.9 集群搭建 - 切片+副本集
1. 环境准备 在Mongo的官网下载Linux版本安装包,然后解压到对应的目录下:由于资源有限,我们采用Replica Sets + Sharding方式来配置高可用.结构图如下所示: 这里我说明下 ...
- 使用 MERGE 语句实现增删改
Ø 简介 在平常编写增删改的 SQL 语句时,我们用的最多的就是 INSERT.UPDATE 和 DELETE 语句,这是最基本的增删改语句.其实,SQL Server 中还有另外一个可以实现增删改 ...
- 9、el表达式的使用
一.EL表达式的作用: 1).使用变量访问web域中存储的对象 ${user } 2).访问javabean的属性 ${user.address.city } 3).执行基本的逻辑运算(el表达式 ...