我们有一个经典模型:

两个串的最长公共后缀长度,是后缀树中两点 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. codevs 1700 施工方案第二季

    1700 施工方案第二季 2012年市队选拔赛北京  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description c国边防 ...

  2. PDF在线预览-pdfjs使用

    请参考我的开源: https://github.com/wuyechun2018/itools/blob/master/src/main/webapp/WEB-INF/views/pdf/index. ...

  3. F2BPM作流引擎系列索引

    索引如下 F2工作流引擎遵循参考WFCM标准规范,符合中国国情特色,更轻量级的工作流引擎,支持多种数据库(mmsqlserver,mysql,oracle),有强大智能的组织模型接口可快速应用到任何基 ...

  4. java 调用 库文件错误查找方法

    第一步首先找到 backtrace:keyword,然后找到都应的库文件 出错的地方 pc 0000088b  /system/lib/libNDK_04.so (SayHello+98). 08-1 ...

  5. QueryString 地址栏参数

    MXS&Vincene  ─╄OvЁ  &0000002 ─╄OvЁ  MXS&Vincene MXS&Vincene  ─╄OvЁ:今天很残酷,明天更残酷,后天很美好 ...

  6. JSP 获取Request 经常使用參数

    <input type="hidden" id="a" value="<%=request.getScheme()%>" ...

  7. leetcode二分查找相关

    目录 33/81搜索旋转排序数组 34在排序数组中查找元素的第一个和最后一个位置 35搜索插入位置 74搜索二维矩阵 300最长上升子序列,354俄罗斯套娃信封问题 33/81搜索旋转排序数组 假设按 ...

  8. TPshop规格组合错误

    TPshop规格组合错误 修改: admin/logic/goodslogic.class.php 中 方法:getSpecInput() 中 asort($spec_arr_sort) 去掉

  9. 【专题系列】单调队列优化DP

    Tip:还有很多更有深度的题目,这里不再给出,只给了几道基本的题目(本来想继续更的,但是现在做的题目不是这一块内容,以后有空可能会继续补上) 单调队列——看起来就是很高级的玩意儿,显然是个队列,而且其 ...

  10. BZOJ 3727 DP?推式子..

    思路: 设$sum[i]表示i的子树中a[i]的和$ $b[1]=\Sigma a[i]*dis[i] = \Sigma _{i=2} ^n sum[i]$ $b[x]-b[fa[x]]=sum[1] ...