【树链剖分】洛谷P3379 树链剖分求LCA
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5
4 4 1 4 4
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
样例说明:
该树结构如下:
第一次询问:2、4的最近公共祖先,故为4。
第二次询问:3、2的最近公共祖先,故为4。
第三次询问:3、5的最近公共祖先,故为1。
第四次询问:1、2的最近公共祖先,故为4。
第五次询问:4、5的最近公共祖先,故为4。
故输出依次为4、4、1、4、4。
题解
emmmmmm
这道题听说倍增写得丑了就过不了。。。
所以我正好拿最近刚学的树链剖分练练手
虽然看到题解里有树剖。。。
但我自认为自己写的比较优美。。。
而且最关键的是题解里好多都在说树剖慢。。。
他们怕不是写了假的树剖。。。
下面给出严格【并不】的复杂度证明:
树剖要用到两次dfs,都是O(n)的复杂度
然后如果图是满二叉树的话是最坏情况,但此时查询也是O(logn)的
【显然最坏情况每次一步步取f[top[x]]走,走下来是一个树的深度,也就是一个logn】
所以理论复杂度上界就是O(2n+mlogn)
而且关键的是树剖常数还比倍增要小。。。
倍增的话常数虽然也很小
但是某集训队dalao写过一篇论文里面有严格的树剖的常数证明,计算结果是树剖的常数接近1/2
再加上树剖用的的空间也比倍增啊RMQ啊小
所以写树剖是完全没问题滴~
那么下面就简单说下树剖思路咯
树剖就是把树剖分成若干条不相交的链,目前常用做法是剖成轻重链
所以我们定义siz[x]为以x为根结点的子树的结点个数
对于每个结点x,在它的所有子结点中寻找一个结点y
使得对于y的兄弟节点z,都有siz[y]≥siz[z]
此时x就有一条重边连向y,有若干条轻边连向他的其他子结点【比如z】
这样的话,树上的不在重链上的边的数量就会大大减少
然后我们每次求LCA(x,y)的时候就可以判断两点是否在同一链上
如果两点在同一条链上我们只要找到这两点中深度较小的点输出就行了
如果两点不在同一条链上
那就找到深度较大的点令它等于它所在的重链链端的父节点即为x=f[top[x]]
直到两点到达同一条链上,输出两点中深度较小的点
代码如下
//by 减维 #include<cstdio> #include<iostream> using namespace std; struct edge{ int to,ne; }e[1000005]; int n,m,s,ecnt,head[500005],dep[500005],siz[500005],son[500005],top[500005],f[500005]; void add(int x,int y) { e[++ecnt].to=y; e[ecnt].ne=head[x]; head[x]=ecnt; } void dfs1(int x) { siz[x]=1; dep[x]=dep[f[x]]+1; for(int i=head[x];i;i=e[i].ne) { int dd=e[i].to; if(dd==f[x])continue; f[dd]=x; dfs1(dd); siz[x]+=siz[dd]; if(!son[x]||siz[son[x]]<siz[dd]) son[x]=dd; } } void dfs2(int x,int tv) { top[x]=tv; if(son[x])dfs2(son[x],tv); for(int i=head[x];i;i=e[i].ne) { int dd=e[i].to; if(dd==f[x]||dd==son[x])continue; dfs2(dd,dd); } } int main() { scanf("%d%d%d",&n,&m,&s); for(int i=1;i<n;++i) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs1(s); dfs2(s,s); for(int i=1;i<=m;++i) { int x,y; scanf("%d%d",&x,&y); while(top[x]!=top[y]) { if(dep[top[x]]>=dep[top[y]])x=f[top[x]]; else y=f[top[y]]; } printf("%d\n",dep[x]<dep[y]?x:y); } }