我们有一个经典模型:

两个串的最长公共后缀长度,是后缀树中两点 LCA 的深度.

直接求 LCA 似乎有些困难,不妨这样想 :

设两个串在后缀树中对应的点分别为 $a,b$,将 $a$ 到根的路径涂色,$b$ 向根爬,遇到的第一个涂色点即为 $a$ 与 $b$ 的 LCA.

我们用 $LCT$ 来维护 这颗树,涂色操作直接 $Access$ 并区间赋值.

此时树的形态是怎样的 ?

$a$ 点到根的路径是一个重链,$b$ 向上爬肯定会爬到 $a$ 所在的重链上.

直接爬肯定会很慢,不妨直接 $Access(b)$ ?

考虑 $Access$ 时的语句 : $y=x,x=f_{x}$.

我们在 $Access(b)$ 过程中碰到的第一个被涂色的点其实就是我们要求的 LCA.

尤其当被涂色的点变多的时候,每次 $Access$ 的复杂度就是均摊 $log_{2}{n}$ 的了.

是不是十分神奇 ?

观察这部分代码:

void Access(int x,int co)
{
int t=0;
while(x)
{
splay(x);
if(pos[x]) tr::update(1,n,1,pos[x],dis[x]);
pos[x]=co, rson=t, t=x,x=f[x];
}
}

我们一边 $Access$ ,一边将该询问点向上爬,每次遇到一个新的重链就将重链染成当前颜色(区间中的下标).可能会好奇为什么只更新那个下标更小的呢 ?

因为如果更新下标大的,而不更新下标小的,会出现这种情况:

当前询问区间左端点大于下标小的那个,那么显然下标大的继承不了下标小的的贡献.

为了避免这种情况,也就是说希望贡献给对未来没有影响的那个,我们只更新给下标更小的那个. (如果询问左端点小于下标小的点的话一定是能覆盖到的)

而下标大的点如何处理呢 ?

既然不对答案做贡献,我们将遇到的每一个重链都染成更大的下标. (显然我们以后要染的下标肯定都会大于该下标,所以肯定下标越大越好).

最难的部分处理完毕了,查询的时候直接在对应的线段树里查一个区间最大值即可.  

#include<bits/stdc++.h>
#define maxn 200003
using namespace std;
void setIO(string s)
{
string in=s+".in", out=s+".out";
freopen(in.c_str(),"r",stdin);
// freopen(out.c_str(),"w",stdout);
}
int n,Q;
int dis[maxn],track[maxn];
char str[maxn];
namespace tr
{
int maxv[maxn<<2];
void update(int l,int r,int x,int k,int p)
{
if(l==r)
{
maxv[x]=p;
return;
}
int mid=(l+r)>>1;
if(k<=mid) update(l,mid,(x<<1),k,p);
else update(mid+1,r,(x<<1)|1,k,p);
maxv[x]=max(maxv[x<<1],maxv[(x<<1)|1]);
}
int query(int l,int r,int x,int L,int R)
{
if(l>=L&&r<=R) return maxv[x];
int mid=(l+r)>>1,tmp=0;
if(L<=mid) tmp=max(tmp,query(l,mid,x<<1,L,R));
if(R>mid) tmp=max(tmp,query(mid+1,r,(x<<1)|1,L,R));
return tmp;
}
};
namespace tree
{
#define lson ch[x][0]
#define rson ch[x][1]
int f[maxn],ch[maxn][30],pos[maxn],sta[maxn];
int get(int x)
{
return ch[f[x]][1]==x;
}
int isrt(int x)
{
return !(ch[f[x]][0]==x||ch[f[x]][1]==x);
}
void pushdown(int x)
{
if(!x)return;
if(pos[x])
{
if(lson) pos[lson]=pos[x];
if(rson) pos[rson]=pos[x];
}
}
void rotate(int x)
{
int old=f[x], fold=f[old],which=get(x);
if(!isrt(old)) ch[fold][ch[fold][1]==old]=x;
ch[old][which]=ch[x][which^1],f[ch[old][which]]=old;
ch[x][which^1]=old,f[old]=x,f[x]=fold;
}
void splay(int x)
{
int u=x,v=0,fa;
sta[++v]=u;
while(!isrt(u)) sta[++v]=f[u],u=f[u];
while(v) pushdown(sta[v--]);
for(u=f[u];(fa=f[x])!=u;rotate(x))
if(f[fa]!=u)
rotate(get(fa)==get(x)?fa:x);
}
void Access(int x,int co)
{
int t=0;
while(x)
{
splay(x);
if(pos[x]) tr::update(1,n,1,pos[x],dis[x]);
pos[x]=co, rson=t, t=x,x=f[x];
}
}
};
namespace SAM
{
int last,tot;
int ch[maxn][30],f[maxn];
void init()
{
last=tot=1;
}
void ins(int c,int o)
{
int np=++tot,p=last;
last=np,dis[np]=dis[p]+1;
while(p&&!ch[p][c]) ch[p][c]=np,p=f[p];
if(!p) f[np]=1;
else
{
int q=ch[p][c];
if(dis[q]==dis[p]+1) f[np]=q;
else
{
int nq=++tot;
dis[nq]=dis[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
f[nq]=f[q], f[np]=f[q]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p]; // 割裂操作
}
}
track[o]=np;
}
};
int answer[maxn];
struct OPT
{
int l,r,id;
}opt[maxn];
bool cmp(OPT a,OPT b)
{
return a.r<b.r;
}
int main()
{
// setIO("input");
SAM::init();
scanf("%d%d",&n,&Q);
scanf("%s",str+1);
for(int i=1;i<=n;++i) SAM::ins(str[i]-'0',i);
for(int i=2;i<=SAM::tot;++i) tree::f[i]=SAM::f[i];
for(int i=1;i<=Q;++i) scanf("%d%d",&opt[i].l,&opt[i].r),opt[i].id=i;
sort(opt+1,opt+1+Q,cmp);
for(int i=1,j=1;i<=n;++i)
{
tree::Access(track[i], i); // 打上 i 点的标记
while(opt[j].r==i && j<=Q)
{
answer[opt[j].id]=tr::query(1,n,1,opt[j].l,opt[j].r);
++j;
}
}
for(int i=1;i<=Q;++i) printf("%d\n",answer[i]);
return 0;
}

  

LOJ #6041. 「雅礼集训 2017 Day7」事情的相似度 LCT+SAM+线段树的更多相关文章

  1. 【刷题】LOJ 6041 「雅礼集训 2017 Day7」事情的相似度

    题目描述 人的一生不仅要靠自我奋斗,还要考虑到历史的行程. 历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势. 你发现在历史的不同时刻,不断的有相同的事情发 ...

  2. LOJ #6041. 「雅礼集训 2017 Day7」事情的相似度

    我可以大喊一声这就是个套路题吗? 首先看到LCP问题,那么套路的想到SAM(SA的做法也有) LCP的长度是它们在parent树上的LCA(众所周知),所以我们考虑同时统计多个点之间的LCA对 树上问 ...

  3. loj#6041. 「雅礼集训 2017 Day7」事情的相似度(SAM set启发式合并 二维数点)

    题意 题目链接 Sol 只会后缀数组+暴躁莫队套set\(n \sqrt{n} \log n\)但绝对跑不过去. 正解是SAM + set启发式合并 + 二维数点/ SAM + LCT 但是我只会第一 ...

  4. loj#6041. 「雅礼集训 2017 Day7」事情的相似度(后缀自动机+启发式合并)

    题面 传送门 题解 为什么成天有人想搞些大新闻 这里写的是\(yyb\)巨巨说的启发式合并的做法(虽然\(LCT\)的做法不知道比它快到哪里去了--) 建出\(SAM\),那么两个前缀的最长公共后缀就 ...

  5. #6041. 「雅礼集训 2017 Day7」事情的相似度 [set启发式合并+树状数组扫描线]

    SAM 两个前缀的最长后缀等价于两个点的 \(len_{lca}\) , 题目转化为求 \(l \leq x , y \leq r\) , \(max\{len_{lca(x,y)}\}\) // p ...

  6. 「雅礼集训 2017 Day7」事情的相似度

    「雅礼集训 2017 Day7」事情的相似度 题目链接 我们先将字符串建后缀自动机.然后对于两个前缀\([1,i]\),\([1,j]\),他们的最长公共后缀长度就是他们在\(fail\)树上对应节点 ...

  7. 【LOJ 6041】「雅礼集训 2017 Day7」事情的相似度

    Description 人的一生不仅要靠自我奋斗,还要考虑到历史的行程. 历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势. 你发现在历史的不同时刻,不断的 ...

  8. LOJ6041. 「雅礼集训 2017 Day7」事情的相似度 [后缀树,LCT]

    LOJ 思路 建出反串的后缀树,发现询问就是问一个区间的点的\(lca\)的深度最大值. 一种做法是dfs的时候从下往上合并\(endpos\)集合,发现插入一个点的时候只需要把与前驱后继的贡献算进去 ...

  9. 【loj6041】「雅礼集训 2017 Day7」事情的相似度 后缀自动机+STL-set+启发式合并+离线+扫描线+树状数组

    题目描述 给你一个长度为 $n$ 的01串,$m$ 次询问,每次询问给出 $l$ .$r$ ,求从 $[l,r]$ 中选出两个不同的前缀的最长公共后缀长度的最大值. $n,m\le 10^5$ 题解 ...

随机推荐

  1. 贪心算法 Heidi and Library (easy)

    A. Heidi and Library (easy) time limit per test 2 seconds memory limit per test 256 megabytes input ...

  2. N - Corporate Identity

    Beside other services, ACM helps companies to clearly state their “corporate identity”, which includ ...

  3. Java 和JS Base64加密

    项目在登录.注册等场景实现时,经常会用到用户信息前端加密,然后项目后端二次解密,避免信息直接在浏览器上以明文显示. 本文主要介绍了base64加密的方式处理代码,不支持中文 源码如下: base64. ...

  4. JSP中page、request、session、application作用域的使用

    几乎所有的Web开发语言都支持Session功能,Servlet也不例外. Servlet/JSP中的Session功能是通过作用域(scope)这个概念来实现的. 作用域分为四种,分别为: page ...

  5. 小米手机 js 脚本取src为空的适配问题

    今天測试提上来一个问题 我android webview 中运行了一段js脚本.去替换原来的图片.可是小米手机上竟然没起作用 花了一个中午的午休看问题 贴出来帮助下遇到相同的问题的朋友吧.我百度了半天 ...

  6. 民意调查Django实现(三)

    我们接着第二小节的開始继续我们的旅程. 我们会继续Web-poll应用.而且将会专注于创建公共接口 - "Views". 哲学思想 一个视图是你的Django应用中的一个Web页面 ...

  7. HTML导航 - 点击更改背景

    步骤一: 在须要添加效果的<li>标签中添加onclick事件:<li onclick="setcurrent(this)"> 步骤二: 加入JS代码: f ...

  8. HashMap源代码剖析

    大部分思路都是一样的 .仅仅是一些细节不一样.源代码中都标了出来.jdk容器源代码还是挺简单的. public class HashMap<K,V> extends AbstractMap ...

  9. 判断文件是否为空 C++

    #include <sys/stat.h> int stat(const char *restrict pathname, struct stat *restrict buf); stru ...

  10. C++ 对象的赋值和复制 基本的

    对象的赋值 如果对一个类定义了两个或多个对象,则这些对象之间是可以进行赋值,或者说,一个对象的值可以赋值给另一个同类的对象.这里所指的值是指对象中所有数       据的成员的值.对象之间进行赋值是“ ...