LCA&树链剖分在树的区间、节点的公共祖先问题&&st在线查询&RMQ算法补充

首先
节点的公共祖先:
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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值