首先
节点的公共祖先:
LCA、树链剖分都可以找
树链剖分找节点的公共祖先:
top数组来看是否在一条链上,不一条链上的话就让深度大(也就是h小的往上爬),直到最后他们在一条链上,然后深度小的就是最近公共祖先
//
// main.cpp
// LCA&树链剖分
//
// Created by 陈冉飞 on 2019/8/2.
// Copyright © 2019 陈冉飞. All rights reserved.
//
//#include <bits/stdc++.h>
#include <iostream>
#include <cmath>
#include <vector>
typedef long long ll;
#define maxn 30050
using namespace std;
int w[205];
vector<int> g[maxn];
//dfs1
int fa[maxn],h[maxn],siz[maxn],son[maxn];
//dfs2
int val[maxn],top[maxn],pos[maxn],A[maxn],cnt = 0,n; //n为节点数
//建树update
int tree[maxn<<3],mx[maxn<<3]; //mx是用来储存最大值的,而tree是用用来储存和的
void dfs1(int u,int f){
//首先赋上父节点的值
fa[u] = f;
//然后再赋上深度
h[u] = h[f] + 1;
//size要赋上值
siz[u] = 1;
//重儿子也要初始化
son[u] = 0;
//定义一个索引,是u这个节点相邻的
int v;
for (int i = 0; i < g[u].size(); i++) {
//拿到与u这个节点相邻的所有节点的索引
v = g[u][i];
//首先要确认这个节点是不是父亲节点,防止刷新了父亲节点的值,不是的话再传入下一层的dfs1中遍历
if (v != f) {
dfs1(v, u);
//然后当从子节点中返回来之后,刷新当前层的值
siz[u] += siz[v];
//判断是否更新当前节点u的重儿子的值
if(siz[son[u]] < siz[v]){ //如果大于的话,就刷新当前的son的值
son[u] = v;
}
}
}
}
void dfs2(int u,int f,int k){ //k为当前的这个节点的所在链的链首
//更新当前节点的所在链的链首
top[u] = k;
//更新pos
pos[u] = ++cnt;
//将数据映射到A里边
A[cnt] = val[u];
if (son[u] != 0) { //如果重儿子存在,不为零,就延伸当前的链
dfs2(son[u], u, k);
}
//然后开始遍历所有的相邻的情况
int v; //定义一个索引
for (int i = 0; i < g[u].size(); i++) {
v = g[u][i];
//新创一条链就应该让新增节点不等于最大的上边已经创了链的节点
if (v != f && son[u] != v) {
dfs2(v, u, v);
}
}
}
int LCA(int a, int b)
{
int ans = 0;
while (1)
{
// cout<<a<<" "<<b<<" "<<top[a]<<" "<<top[b]<<" "<<h[top[a]]<<" "<<h[top[b]]<<endl;
//如果在一条链上
if (top[a] == top[b]){
if(h[a] <= h[b]){
//ans += a 到 b的距离 然后return ans
return a;
}
return b;
// return h[a] <= h[b] ? a : b;
}//后面这两个都是top不一样的情况
//a的top比较大,所以a更靠下,要把a往上升
else if (h[top[a]] >= h[top[b]]){
a = fa[top[a]];
}
//b的top大
else {
b = fa[top[b]];
}
}
}
int main(int argc, const char * argv[]) {
int T,qu_total;
scanf("%d",&T);
while (T--) {
int i,a,b;
scanf("%d%d",&n,&qu_total);
for (i = 1; i < n ; i++) {
scanf("%d%d",&a,&b);
g[a].push_back(b);
g[b].push_back(a);
}
dfs1(1, 0);
//检验输出
// for(i = 0;i < 30000+50;i++){
// if (g[i].size() != 0) {
// cout<<"*****************"<<endl;
// for (int j = 0; j < g[i].size(); j++) {
// cout<<g[i][j]<<" ";
// }
// cout<<endl;
// cout<<"父节点 "<<fa[i]<<" 深度 "<<h[i]<<" size "<<siz[i]<<" 重儿子 "<<son[i]<<endl;
// }
// }
dfs2(1, 0, 1);
//检验输出
// cout<<"一共的链数 "<<cnt<<endl;
// for(i = 1;i <= cnt; i ++){
// cout<<i<<" "<<A[i]<<endl;
// }
// for (i = 0; i < 30000+50; i++) {
// if (g[i].size() != 0) {
// cout<<"*****************"<<endl;
// for (int j = 0; j < g[i].size(); j++) {
// cout<<g[i][j]<<" ";
// }
// cout<<endl;
// cout<<"父节点 "<<fa[i]<<" 深度 "<<h[i]<<" size "<<siz[i]<<" 重儿子 "<<son[i]<<" 所在的链的链首 "<<top[i]<<" 所在的序号数 "<<pos[i]<<endl;
// }
// }
int tema,temb;
while (qu_total--) {
scanf("%d%d",&tema,&temb);
cout<<LCA(tema, temb)<<endl;
}
}
return 0;
}
LCA求公共祖先
//
// main.cpp
// LCA_最近公共祖先
//
// Created by 陈冉飞 on 2019/8/3.
// Copyright © 2019 陈冉飞. All rights reserved.
//
#include <iostream>
#include <cstring>
using namespace std;
#define cl(a,b) memset(a,b,sizeof(a))
typedef long long ll;
#define maxn 40050
struct node{
int to,w,next;
}edge[2*maxn];
int k,total,qu_total;
int fa[maxn],head[maxn],vis[maxn],dis[maxn]; //fa类似并查集中的标志这个值属于那个集合,
int bg[500],ed[500],lca[500]; //lca数组用来储存所有的公共祖先的位置
int findfa(int x){
if (fa[x] != x) {
fa[x] = findfa(fa[x]);
return fa[x]; //找现在父亲的父亲
}
return fa[x];
}
void add(int u,int v,int w){
//添加边的属性都一样,都有一个head[]数组来记录边的属性
edge[k].to = v;
edge[k].w = w;
edge[k].next = head[u];
head[u] = k++;
}
//把最一开始的头节点传进去,然后计算所有的相关的长度
void tarjan(int u){
int v,i;
//先初始化
fa[u] = u;
vis[u] = 1;
for (i = 0; i < qu_total; i++) {
if (ed[i] == u && vis[bg[i]]) {
lca[i] = findfa(bg[i]);
}
if (vis[ed[i]] && bg[i] == u) {
lca[i] = findfa(ed[i]);
}
}
//然后套spfa模版,更新距离
for (i = head[u]; i; i = edge[i].next) { //往后指引到next
v = edge[i].to;
if (vis[v] == 0) { //未被访问过
dis[v] = dis[u] + edge[i].w;
tarjan(v);
fa[v] = u; //回溯父节点
}
}
}
int main(int argc, const char * argv[]) {
int T,u,v,w,i;
scanf("%d",&T);
while (T--) {
k = 1;
cl(vis,0);
cl(head, 0);
cl(dis,0);
scanf("%d%d",&total,&qu_total);
for (i = 0; i < total-1; i ++) {
scanf("%d%d%d",&u,&v,&w);
add(u, v, w);
add(v, u, w); //注意添加反向的边
}
//后来是计算两个点之间的距离
for (i = 0; i < qu_total; i++) {
scanf("%d%d",&bg[i],&ed[i]);
}
tarjan(1);//从第一个节点开始计算,通过递归然后以传递的方式慢慢赋上值
for (i = 0; i < qu_total; i++) {
//计算最后的ans,距离等于起点和终点到跟节点的和减去二倍的
cout<<dis[bg[i]]+dis[ed[i]]-2*dis[lca[i]]<<endl;
}
}
return 0;
}
最后通过输出lca[i]即为bg[i] 和 ed[i]的公共最近祖先。
然后也可以计算两个节点的公共在树上的距离,即为两个节点到根节点的距离之和-这两个节点的公共祖先这个节点到根节点的距离乘以二。
区间最值问题
每个节点赋值:树链剖分:树链剖分板题bzoj1036
节点与节点之间的连线的一部分赋值:LCA
要用到结构体将每条线的数据储存在结构体中,起点+终点+这段距离的权值。
st表 利用st表可以在线处理查询树上结构问题。(而上面的Tarjan算法是离线算法,时间复杂度稳定在O(m+n),m为查询的个数,n为所有的点数。)
- 关于在线、离线,在线就是边输入边处理,然后离线就是先把所有的数据都输入,然后再统一处理。
- 在线的例子如 可持续化线段树(主席树)、可持续化字典树等(此外一般的做题思路都是在线思维,边输入边处理)
- 离线的例子如 整体二分、CDQ、分块莫队
补充RMQ算法:一个用于快速返回区间最值的算法
一般想法:遍历一遍,O(n),但是当数据很多时就会tle。
RMQ算法:通过一个logn的预处理的代价(主要时间花在了时间的预处理上。)然后查询的时候O(1)的复杂度。
用空间换时间,开了辅助的数组,在输入数组的所有数的时候就开始记录区间的最值
RMQ预处理就是利用二进制将区间分割成 1 2 4 8……等等这样的子区间,并往这些子区间中存上最值,然后在查询的时候可以用到预处理的子区间的最值(数组索引的复杂度为O(1)),最后预处理的复杂度将区间长度一半变化就是logn。
板子
//
// main.cpp
// RMQ 数列快速返回最值
//
// Created by 陈冉飞 on 2019/8/3.
// Copyright © 2019 陈冉飞. All rights reserved.
//
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define max_length 100100
int max_num[max_length][20];
int min_num[max_length][20];
int main(){
int n;
scanf("%d",&n);
int tem;
//第一部分的预处理,也可以理解为输入,此时找的是长度为1的区间的最值,也就是本身,后面的部分是找区间为2 4 8……的长度的区间的最值
for (int i = 0; i < n; i++) {
scanf("%d",&tem);
max_num[i][0] = tem;
min_num[i][0] = tem;
}
//第二部分预处理
for (int j = 1; (1<<j) <= n; j++) {
for (int i = 1; i+(1<<j)-1 <= n; i++) {
//看前后两个区间的最值 j-1 就是二进制前走一位,也就是区间的长度变成一半,然后比较区间最值的时候,不同的地方是起点。
max_num[i][j] = max(max_num[i][j-1],max_num[i+(1<<(j-1))][j-1]);
min_num[i][j] = min(min_num[i][j-1],min_num[i+(1<<(j-1))][j-1]);
}
}
//然后开始查询
int l,r,pos = 0;
scanf("%d%d",&l,&r);
//首先用pos进行移位,找到对应的区间长度(这里指的长度是比他小的两个区间的长度,这样比较才能让这个区间充分填充),而pos表示对应的二进制的长度
while ((1<<(pos+1)) <= r-l+1) pos++;
cout<<max(max_num[l][pos],max_num[r-(1<<pos)+1][pos]);
cout<<min(min_num[l][pos],min_num[r-(1<<pos)+1][pos]);
return 0;
}