dfs序+ST表(lca模板)

http://poj.org/problem?id=1330

题意:给出一颗树,给出父子关系,求两点的lca。

解法:dfs序+ST表:

原理:

欧拉序(前序遍历得到的序列,叫dfs序,但数字可以重复出现,一进一出,叫欧拉序),会发现根结点总在中间,而根结点是该段序列深度最小的点

因此两个点的LCA,就是在该序列上两个点第一次出现的区间内深度最小的那个点

即在欧拉序中进行ST表在区间中找到最小深度的欧拉序。

dfs序+ST表(lca模板)

 

 该欧拉序为:ABDBEGEBCFHFCA.

如果要求D、G的lca通过ST表查询D、G间深度最小的节点B即为D、G的lca。

问题一:如何将图转为欧拉序。

问题二:如何在欧拉序与节点间转换。

见代码:

//#include<bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
#include <stdio.h>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <string.h>
#include <vector>
typedef long long ll ;
#define mod 1000000007
#define gcd __gcd
#define rep(i , j , n) for(int i = j ; i <= n ; i++)
#define red(i , n , j)  for(int i = n ; i >= j ; i--)
#define ME(x , y) memset(x , y , sizeof(x))
//ll lcm(ll a , ll b){return a*b/gcd(a,b);}
//ll quickpow(ll a , ll b){ll ans=1;while(b){if(b&1)ans=ans*a%mod;b>>=1,a=a*a%mod;}return ans;}
//int euler1(int x){int ans=x;for(int i=2;i*i<=x;i++)if(x%i==0){ans-=ans/i;while(x%i==0)x/=i;}if(x>1)ans-=ans/x;return ans;}
//const int N = 1e7+9; int vis[n],prime[n],phi[N];int euler2(int n){ME(vis,true);int len=1;rep(i,2,n){if(vis[i]){prime[len++]=i,phi[i]=i-1;}for(int j=1;j<len&&prime[j]*i<=n;j++){vis[i*prime[j]]=0;if(i%prime[j]==0){phi[i*prime[j]]=phi[i]*prime[j];break;}else{phi[i*prime[j]]=phi[i]*phi[prime[j]];}}}return len}
#define INF  0x3f3f3f3f
#define PI acos(-1)
#define pii pair<int,int>
#define fi first
#define se second
#define lson l,mid,root<<1
#define rson mid+1,r,root<<1|1
#define int ll
#define cin() scanf("%lld" , &x);
using namespace std;
const int maxn = 1e4+9;
int head[maxn] , tol , is_root[maxn] , root;//图、判断根节点
int oula[maxn<<1] , de[maxn<<1] , pos[maxn] , len;//欧拉序、欧拉序对应的深度、节点在欧拉数组中的映射、欧拉序长(2*n-1).
int dp[maxn<<1][22];//对欧拉序的st表
struct node{
    int to , next;
}g[maxn<<1];

void init(){
    ME(head , 0);
    ME(is_root , 0);
    len = 0 , tol = 0;
}

void add(int u , int v){
    g[++tol] = {v , head[u]};
    head[u] = tol;
}

void dfs(int u , int pre , int d){
    oula[++len] = u ;//进
    pos[u] = len ;//记录节点在欧拉数组的下标
    de[len] = d ;//记录欧拉序深度
    for(int i = head[u] ; i ; i = g[i].next){
        int v = g[i].to ;
        if(v == pre) continue;
        dfs(v , u , d+1);
        oula[++len] = u ;//出
        de[len] = d ;//欧拉序深度
    }
}

int Min(int x , int y){//返回欧拉序深度最小
    return de[x] > de[y] ? y : x;
}

void ST(){
    rep(i , 1 , len){
        dp[i][0] = i ;
    }
    for(int j = 1 ; (1<<j) <= len ; j++){
        for(int i = 1 ; i+(1<<j)-1<=len ; i++){
            dp[i][j] = Min(dp[i][j-1] , dp[i+(1<<(j-1))][j-1]);//记录欧拉序区间深度最小的欧拉序
        }
    }
}

int lca(int x , int y){
    x = pos[x] , y = pos[y];//映射到欧拉数组下标
    if(x > y) swap(x , y);//使成为有效区间
    int k = log2(y-x+1) ;
    return Min(dp[x][k] , dp[y-(1<<k)+1][k]);//返回的是欧拉序
}

void solve(){
    int n ;
    cin >> n ;
    init();
    rep(i , 1 , n-1){
        int u , v ;
        cin >> u >> v ;
        add(u , v);
        add(v , u);
        is_root[v] = 1 ;
    }
    rep(i , 1 , n)
        if(!is_root[i]) root = i ;
    dfs(root , 0 , 1);
    ST();
    int u , v ;
    cin >> u >> v ;
    cout << oula[lca(u , v)]<< endl;//需要通过欧拉序映射回节点

}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int t ;
    cin >> t ;
    while(t--){
        solve();
    }
}

 

上一篇:LCA题目选讲3


下一篇:洛谷网校:树形问题(持续更新)