關於樹

樹的直徑

樹的直徑是指樹中兩個點之間的最大距離,即最長的一條鏈。node

容易證實(信奧不須要證實),樹的直徑的起始點是該樹的葉子節點。spa

求樹的直徑通常有兩種方法,一種是兩遍\(dfs\),另外一種是樹形\(DP\).net

兩遍dfs

咱們隨便從一個點\(u\)出發,而後去尋找一個離這個點最遠的一個點\(v\)code

而後再從這個點v出發去尋找最長鏈。class

void dfs(int x,int p) {
    for(re int i = head[x] ; i ; i = t[i].next) {
        int v = t[i].to;
        if(vis[v]) continue;
        vis[v]=1;
        dis[v] = p + t[i].dis;
        if(dis[v] > ans) {
            ans=dis[v];
            node=v;
        }
        dfs(v,dis[v]);
    }
}

樹形DP

\(f[x][1]\)維護在x的子樹中的最長鏈。遍歷

\(f[x][2]\)維護在x的子樹中的次長鏈。方法

那麼在\(x\)的子樹中的最長鏈就是\(f[x][1]+f[x][2]\).next

void dfs(int x, int fa) {
    for(re int i = head[x]; i; i = t[i].net) {
        int v = t[i].to;
        if(v == fa) continue; dfs(v,x);
        if(l[v][1] + t[i].dis > l[x][1])
            l[x][2] = l[x][1], l[x][1] = l[v][1] + t[i].dis;
        else if(l[v][1] + t[i].dis > l[x][2])
            l[x][2] = l[v][1] + t[i].dis;
        if(lian < l[x][1]+l[x][2]) lian = l[x][1] + l[x][2];
    }
}

樹上求LCA

LCA(Lowest Common Ancestors)即最近公共祖先,top

是指在有根樹中,找出某兩個結點\(u\)\(v\)最近的公共祖先.di

目前只會兩種方法,倍增和樹鏈剖分。

倍增求LCA

f[x][i]是指節點x向上第\(2^i\)個節點。

有一個關鍵的式子:\(f[x][i]=f[f[x][i-1]][i-1].\)

void dfs(int x, int fa) {
    f[x][0] = fa; deep[x] = deep[fa] + 1;
    for(re int i = 1; (1 << i) <= deep[x]; ++ i) //不超過根節點
        f[x][i] = f[f[x][i-1]][i-1];
    for(re int i = head[x]; i; i = t[i].net)
        if(t[i].to != fa) dfs(t[i].to, x);
}
int lca(int x, int y) {
    if(deep[x] < deep[y]) std :: swap(x,y); // 讓x爲深度更大的
    for(re int i = 21; i >= 0; -- i)
        if(deep[x] - (1 << i) >= deep[y]) // 使x的深度始終大於或等於y
            x = f[x][i];
    if(x == y) return x;
    for(re int i = 21; i >= 0; -- i)
        if(f[x][i] == f[y][i]) continue;
        else x = f[x][i], y = f[y][i];
    return f[x][0]; // x的父親必定是LCA
}

樹鏈剖分求LCA

每個節點都去維護一個子樹大小,深度,鏈頂,父親,最重鏈(最大子樹)。

第一次\(dfs\)去求深度,父親和子樹大小。

第二次\(dfs\)去求鏈頂。

\(LCA\)的時候,不斷的更新鏈頂深度大的,使其在同一深度。

\(LCA\)就是深度小的那個。

void dfs_1(int x) {
    deep[x] = deep[fa[x]] + 1; size[x] = 1;
    for(re int i = head[x]; i; i = t[i].net) {
        int v = t[i].to;
        if(fa[x] == v) continue; // 父親已經遍歷了
        fa[v] = x; dfs_1(v);
        size[x] += size[v];
        if(size[son[x]] < size[v] || !son[x]) son[x] = v;
        //沒有兒子或者是兒子的子樹重量沒有當前子樹重量大,都會更新
    }
}
void dfs_2(int x, int tp) {
    top[x] = tp; // 一條鏈上的人鏈頂都相同
    if(son[x]) dfs_2(son[x],tp); // 還有兒子
    for(re int i = head[x]; i; i = t[i].net) {
        int v = t[i].to;
        if(fa[x] == v || son[x] == v) continue;
        // 父親和兒子都遍歷過了
        dfs_2(v, v);
    }
}
int lca(int x, int y) {
    while(top[x] != top[y]) { // 更新深度大的
        if(deep[top[x]] < deep[top[y]]) y = fa[top[y]];
        else x = fa[top[x]];
    }
    return deep[x] < deep[y] ? x : y;
}
相關文章
相關標籤/搜索