求树上两条路径的 LCP (树上每个节点代表一个字符)

总共写+调了6个多小时,终于过了~

绝对是我写过的最复杂的数据结构了

我们对这棵树进行轻重链剖分,然后把所有的重链分正串,反串插入到广义后缀自动机中.

求 LCP 的话就是后缀树中两点 $LCA$ 的深度.

如果 $LCP$ 的长度小于两个重链的长度,就直接输出答案,否则还要继续爬重链.

这道题恶心之处在于将所有重链不重不漏存起来,无法形容有多恶心.

code:

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
#include <stack>
#include <string>
using namespace std;
void setIO(string s)
{
string in=s+".in";
string out=s+".out";
freopen(in.c_str(),"r",stdin);
freopen(out.c_str(),"w",stdout);
}
namespace sam
{
#define N 1200004
int last,tot;
int ch[N][27],pre[N],mx[N];
inline int extend(int c)
{
if(ch[last][c])
{
int p=last;
int q=ch[p][c];
if(mx[q]==mx[p]+1) last=q;
else
{
int nq=++tot;
mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
pre[nq]=pre[q],pre[q]=nq;
for(;p&&ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
last=nq;
}
}
else
{
int np=++tot,p=last;
mx[np]=mx[p]+1,last=np;
for(;p&&!ch[p][c];p=pre[p]) ch[p][c]=np;
if(!p) pre[np]=1;
else
{
int q=ch[p][c];
if(mx[q]==mx[p]+1) pre[np]=q;
else
{
int nq=++tot;
mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
pre[nq]=pre[q],pre[np]=pre[q]=nq;
for(;p&&ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
}
}
last=np;
}
return last;
}
#undef N
}; // 优美的后缀自动机 namespace suffix_tree
{
#define LOG 21
#define N 1200006
vector<int>G[N];
int fa[LOG][N],dis[N];
void dfs(int u)
{
int i,j;
dis[u]=dis[sam::pre[u]]+1;
fa[0][u]=sam::pre[u];
for(i=1;i<LOG;++i) fa[i][u]=fa[i-1][fa[i-1][u]];
for(i=0;i<(int)G[u].size();++i) dfs(G[u][i]);
}
inline void build()
{
int i,j;
for(i=2;i<=sam::tot;++i)
G[sam::pre[i]].push_back(i);
dfs(1);
}
inline int jump(int x,int len)
{
for(int i=LOG-1;i>=0;--i) if(sam::mx[fa[i][x]]>=len) x=fa[i][x];
return x;
}
inline int LCA(int x,int y)
{
if(dis[x]!=dis[y])
{
if(dis[x]>dis[y]) swap(x,y);
for(int i=LOG-1;i>=0;--i) if(dis[fa[i][y]]>=dis[x]) y=fa[i][y];
}
if(x==y) return x;
for(int i=LOG-1;i>=0;--i) if(fa[i][x]!=fa[i][y]) x=fa[i][x], y=fa[i][y];
return fa[0][x];
}
#undef N
#undef LOG
}; // 优美的后缀树 namespace tree
{
#define N 600003
int tot,iii;
int son[N],size[N],dep[N],dfn[N],top[N],fa[N],up[N],down[N],val[N],idx[N],pp[N];
vector<int>G[N];
struct node
{
int u,c;
node(int u=0,int c=0):u(u),c(c){}
};
vector<node>A[N];
inline void add(int u,int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
void dfs1(int u,int ff)
{
fa[u]=ff,dep[u]=dep[ff]+1,size[u]=1;
for(int i=0;i<G[u].size();++i)
{
if(G[u][i]==ff) continue;
dfs1(G[u][i],u);
size[u]+=size[G[u][i]];
if(size[G[u][i]]>size[son[u]]) son[u]=G[u][i];
}
}
void dfs2(int u,int tp)
{
idx[u]=++iii;
pp[iii]=u;
top[u]=tp;
if(tp==u) dfn[tp]=++tot;
A[dfn[tp]].push_back(node(u,val[u]));
if(son[u]) dfs2(son[u],tp);
for(int i=0;i<G[u].size();++i)
if(G[u][i]!=son[u]&&G[u][i]!=fa[u])
dfs2(G[u][i], G[u][i]);
}
inline void build()
{
int i,j;
dfs1(1,0);
dfs2(1,1);
for(i=1;i<=tot;++i)
{
sam::last=1;
// 从上到下 (实际是从下到上)
for(j=0;j<A[i].size();++j)
{
int u=A[i][j].u;
int c=A[i][j].c;
up[u]=sam::extend(c);
}
// 从下到上 (实际上是从上到下)
sam::last=1;
for(j=A[i].size()-1;j>=0;--j)
{
int u=A[i][j].u;
int c=A[i][j].c;
down[u]=sam::extend(c);
}
}
}
int LCA(int x,int y)
{
while(top[x]!=top[y]) dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]];
return dep[x]<dep[y]?x:y;
}
#undef N
}; // 优美的树链剖分 struct node
{
int u,len,ty,tt;
node(int u=0,int len=0,int ty=0,int tt=0):u(u),len(len),ty(ty),tt(tt){}
};
#define N 300003
char S[N];
int ty;
vector<node>A[2];
stack<node>ss;
void insert(int a,int b,int id)
{
int lca=tree::LCA(a,b);
// 向上走
while(tree::dep[a]>tree::dep[lca])
{
int pp=tree::dep[tree::top[a]]<tree::dep[lca]?lca:tree::top[a];
A[id].push_back(node(tree::up[a], tree::dep[a]-max(tree::dep[lca],tree::dep[tree::top[a]])+1,0,a));
a=tree::fa[tree::top[a]];
}
// 向下走
if(a==lca)
{
// 未计算 lca
while(tree::dep[b]>=tree::dep[lca])
{
int pp=tree::dep[tree::top[b]]<tree::dep[lca]?lca:tree::top[b];
ss.push(node(tree::down[pp], tree::dep[b]-max(tree::dep[lca],tree::dep[pp])+1,1,pp));
b=tree::fa[tree::top[b]];
}
}
else
{
while(tree::dep[b]>tree::dep[lca])
{
int pp=tree::dep[tree::top[b]]<tree::dep[lca]?lca:tree::top[b];
ss.push(node(tree::down[pp], tree::dep[b]-max(tree::dep[lca],tree::dep[pp])+1,1,pp));
b=tree::fa[tree::top[b]];
}
}
while(!ss.empty()) A[id].push_back(ss.top()),ss.pop();
}
int main()
{
// setIO("input");
int i,j,n,m;
scanf("%d%s",&n,S+1);
for(i=1;i<=n;++i) tree::val[i]=S[i]-'a';
for(i=1;i<n;++i)
{
int u,v;
scanf("%d%d",&u,&v);
tree::add(u,v);
}
sam::tot=1;
tree::build(); // 建完树剖 + 上/下走定位到后缀自动机的位置
suffix_tree::build(); // 建完后缀树
scanf("%d",&m);
for(i=1;i<=m;++i)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
insert(a,b,0);
insert(c,d,1);
int t1=0,t2=0,lcp=0;
while(t1<A[0].size()&&t2<A[1].size())
{
node a1=A[0][t1], a2=A[1][t2];
int lca;
int uu=a1.u;
int vv=a2.u;
int u=a1.tt;
int v=a2.tt;
int tmp=min(sam::mx[lca=suffix_tree::LCA(uu,vv)], min(a1.len,a2.len));
lcp+=tmp;
if(tmp<min(a1.len,a2.len)) break;
else
{
if(tmp==a1.len) ++t1;
else
{
A[0][t1].u=(A[0][t1].ty==1)?(tree::down[tree::pp[tree::idx[u]+tmp]]):(tree::up[tree::pp[tree::idx[u]-tmp]]);
A[0][t1].tt=(A[0][t1].ty==1)?(tree::pp[tree::idx[u]+tmp]):(tree::pp[tree::idx[u]-tmp]);
A[0][t1].len=A[0][t1].len-tmp;
}
if(tmp==a2.len) ++t2;
else
{
A[1][t2].u=(A[1][t2].ty==1)?(tree::down[tree::pp[tree::idx[v]+tmp]]):(tree::up[tree::pp[tree::idx[v]-tmp]]);
A[1][t2].tt=(A[1][t2].ty==1)?(tree::pp[tree::idx[v]+tmp]):(tree::pp[tree::idx[v]-tmp]);
A[1][t2].len=A[1][t2].len-tmp;
}
}
}
A[0].clear(),A[1].clear();
printf("%d\n",lcp);
}
return 0;
}

  

CF504E Misha and LCP on Tree 后缀自动机+树链剖分+倍增的更多相关文章

  1. CF 504E Misha and LCP on Tree——后缀数组+树链剖分

    题目:http://codeforces.com/contest/504/problem/E 树链剖分,把重链都接起来,且把每条重链的另一种方向的也都接上,在这个 2*n 的序列上跑后缀数组. 对于询 ...

  2. CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并

    这里给出一个后缀自动机的做法. 假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的: 直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以. 然而,想求 $t ...

  3. [LuoguU41039]PION后缀自动机 树链剖分+动态开点线段树

    链接 刚开始看出题人题解都吓蒙掉了,还以为是什么难题,结果就一板子题 思路:对每一个文件名开一棵线段树,然后树剖即可 #include<bits/stdc++.h> #define REP ...

  4. 【bzoj5084】hashit 广义后缀自动机+树链的并+STL-set

    题目描述 你有一个字符串S,一开始为空串,要求支持两种操作 在S后面加入字母C 删除S最后一个字母 问每次操作后S有多少个两两不同的连续子串 输入 一行一个字符串Q,表示对S的操作 如果第i个字母是小 ...

  5. Water Tree CodeForces 343D 树链剖分+线段树

    Water Tree CodeForces 343D 树链剖分+线段树 题意 给定一棵n个n-1条边的树,起初所有节点权值为0. 然后m个操作, 1 x:把x为根的子树的点的权值修改为1: 2 x:把 ...

  6. CF504E Misha and LCP on Tree(树链剖分+后缀树组)

    1A真舒服. 喜闻乐见的树链剖分+SA. 一个初步的想法就是用树链剖分,把两个字符串求出然后hash+二分求lcp...不存在的. 因为考虑到这个字符串是有序的,我们需要把每一条重链对应的字符串和这个 ...

  7. Codeforces Round #329 (Div. 2) D. Happy Tree Party LCA/树链剖分

    D. Happy Tree Party     Bogdan has a birthday today and mom gave him a tree consisting of n vertecie ...

  8. [POJ3237]Tree解题报告|树链剖分|边剖

    关于边剖 之前做的大多是点剖,其实转换到边剖非常简单. 我的做法是每个点的点权记录其到父亲节点的边的边权. 只要solve的时候不要把最上面的点记录在内就可以了. Tree Description Y ...

  9. Spoj Query on a tree SPOJ - QTREE(树链剖分+线段树)

    You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, ...

随机推荐

  1. 虚拟机VMware中安装Ubuntu18.04

    准备工作 Ubuntu 获取地址: 官网 清华镜像站 VMware 获取地址链接 安装过程 Vmware的安装过程此处不在赘述,不清楚如何安装的请自行百度,参见VMware14安装教程 然后就是Vmw ...

  2. net namespace实验

    Net namespace实验 在 Linux 中,网络名字空间可以被认为是隔离的拥有单独网络栈(网卡.路由转发表.iptables)的环境.网络名字空间经常用来隔离网络设备和服务,只有拥有同样网络名 ...

  3. conda命令入坑记

    conda命令入坑记 本人使用的软件版本: TypeError: LoadLibrary() argument 1 must be str, not None 网上太多的资料,大多都是在讲path的设 ...

  4. Istio最佳实践:在K8s上通过Istio服务网格进行灰度发布

    Istio是什么? Istio是Google继Kubernetes之后的又一开源力作,主要参与的公司包括Google,IBM,Lyft等公司.它提供了完整的非侵入式的微服务治理解决方案,包含微服务的管 ...

  5. springMVC关于异常优先级的处理

    优先级 既然在SpringMVC中有两种处理异常的方式,那么就存在一个优先级的问题: 当发生异常的时候,SpringMVC会如下处理: (1)SpringMVC会先从配置文件找异常解析器Handler ...

  6. Linux修改主机名方法

    [root@lyx ~]# vim /etc/hosts   vim代表修改,进入hosts文件进行添加192.168.10.128 hadoop128 [root@lyx ~]# hostname ...

  7. vue单页面应用加入百度统计

    版权声明:本文为CSDN博主「钟文辉」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/qq_39753974/a ...

  8. web前端如何优化自己的代码

    前端的性能优化主要分为三部分: HTML优化 避免 HTML 中书写 CSS 代码,因为这样难以维护. 使用Viewport加速页面的渲染. 使用语义化标签,减少 CSS 代码,增加可读性和 SEO. ...

  9. iOS开发常见的宏定义(实用)

    iOS开发过程中使用一些常用的宏可以提高开发效率,提高代码的重用性:将这些宏放到一个头文件里然后再放到工程中的-Prefix.pch文件中(或者直接放到-Prefix.pch中)直接可以使用,灰常方便 ...

  10. linux uniq命令用法

    uniq命令: 对指定的ASCII文件或标准输入进行唯一性检查,以判断文本文件中重复出现的行,常用于分析日志:查看tcp各个状态连接数,ip或域名连接数排名等等场景,一般与 sort 命令结合使用. ...