【轉】樹鏈剖分

來源:互聯網
上載者:User

標籤:http   os   io   for   問題   ar   div   時間   

“在一棵樹上進行路徑的修改、求極值、求和”乍一看只要線段樹就能輕鬆解決,實際上,僅憑線段樹是不能搞定它的。我們需要用到一種貌似進階的複雜演算法——樹鏈剖分。

    樹鏈,就是樹上的路徑。剖分,就是把路徑分類為重鏈和輕鏈。
    記siz[v]表示以v為根的子樹的節點數,dep[v]表示v的深度(根深度為1),top[v]表示v所在的鏈的頂端節點,fa[v]表示v的父親,son[v]表示與v在同一重鏈上的v的兒子節點(姑且稱為重兒子),w[v]表示v與其父親節點的連邊(姑且稱為v的父邊)線上段樹中的位置。只要把這些東西求出來,就能用logn的時間完成原問題中的操作。

    重兒子:siz[u]為v的子節點中siz值最大的,那麼u就是v的重兒子。
    輕兒子:v的其它子節點。
    重邊:點v與其重兒子的連邊。
    輕邊:點v與其輕兒子的連邊。
    重鏈:由重邊連成的路徑。
    輕鏈:輕邊。

    剖分後的樹有如下性質:
    性質1:如果(v,u)為輕邊,則siz[u] * 2 < siz[v];
    性質2:從根到某一點的路徑上輕鏈、重鏈的個數都不大於logn。
   

    演算法實現:
    我們可以用兩個dfs來求出fa、dep、siz、son、top、w。
    dfs_1:把fa、dep、siz、son求出來,比較簡單,略過。
    dfs_2:⒈對於v,當son[v]存在(即v不是葉子節點)時,顯然有top[son[v]] = top[v]。線段樹中,v的重邊應當在v的父邊的後面,記w[son[v]] = totw+1,totw表示最後加入的一條邊線上段樹中的位置。此時,為了使一條重鏈各邊線上段樹中連續分布,應當進行dfs_2(son[v]);
           ⒉對於v的各個輕兒子u,顯然有top[u] = u,並且w[u] = totw+1,進行dfs_2過程。
           這就求出了top和w。
    將樹中各邊的權值線上段樹中更新,建鏈和建線段樹的過程就完成了。

    修改操作:例如將u到v的路徑上每條邊的權值都加上某值x。
    一般人需要先求LCA,然後慢慢修改u、v到公用祖先的邊。而高手就不需要了。
    記f1 = top[u],f2 = top[v]。
    當f1 <> f2時:不妨設dep[f1] >= dep[f2],那麼就更新u到f1的父邊的權值(logn),並使u = fa[f1]。
    當f1 = f2時:u與v在同一條重鏈上,若u與v不是同一點,就更新u到v路徑上的邊的權值(logn),否則修改完成;
    重複上述過程,直到修改完成。

    求和、求極值操作:類似修改操作,但是不更新邊權,而是對其求和、求極值。
    就這樣,原問題就解決了。鑒於鄙人語言表達能力有限,咱畫圖來看看:


    如右圖所示,較粗的為重邊,較細的為輕邊。節點編號旁邊有個紅色點的表明該節點是其所在鏈的頂端節點。邊旁的藍色數字表示該邊線上段樹中的位置。圖中1-4-9-13-14為一條重鏈。

    當要修改11到10的路徑時。
    第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此時dep[f1] < dep[f2],因此修改線段樹中的5號點,v = 4, f2 = 1;
    第二次迭代:dep[f1] > dep[f2],修改線段樹中10--11號點。u = 2,f1 = 2;
    第三次迭代:dep[f1] > dep[f2],修改線段樹中9號點。u = 1,f1 = 1;
    第四次迭代:f1 = f2且u = v,修改結束。



    題目:spoj375、USACO December Contest Gold Divison, "grassplant"。
    **spoj375據說不“縮行”情況下最短的程式是140+行,我的是128行。

    附spoj375程式(C++):

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 10010;
struct Tedge
{ int b, next; } e[maxn * 2];
int tree[maxn];
int zzz, n, z, edge, root, a, b, c;
int d[maxn][3];
int first[maxn], dep[maxn], w[maxn], fa[maxn], top[maxn], son[maxn], siz[maxn];
char ch[10];

void insert(int a, int b, int c)
{
     e[++edge].b = b;
     e[edge].next = first[a];
     first[a] = edge;
}

void dfs(int v)
{
     siz[v] = 1; son[v] = 0;
     for (int i = first[v]; i > 0; i = e[i].next)
         if (e[i].b != fa[v])
         {
             fa[e[i].b] = v;
             dep[e[i].b] = dep[v]+1;
             dfs(e[i].b);
             if (siz[e[i].b] > siz[son[v]]) son[v] = e[i].b;
             siz[v] += siz[e[i].b];
         }
}

void build_tree(int v, int tp)
{
     w[v] = ++ z; top[v] = tp;
     if (son[v] != 0) build_tree(son[v], top[v]);
     for (int i = first[v]; i > 0; i = e[i].next)
         if (e[i].b != son[v] && e[i].b != fa[v])
             build_tree(e[i].b, e[i].b);
}

void update(int root, int lo, int hi, int loc, int x)
{
     if (loc > hi || lo > loc) return;
     if (lo == hi)
     { tree[root] = x; return; }
     int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
     update(ls, lo, mid, loc, x);
     update(rs, mid+1, hi, loc, x);
     tree[root] = max(tree[ls], tree[rs]);
}

int maxi(int root, int lo, int hi, int l, int r)
{
     if (l > hi || r < lo) return 0;
     if (l <= lo && hi <= r) return tree[root];
     int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
     return max(maxi(ls, lo, mid, l, r), maxi(rs, mid+1, hi, l, r));
}

inline int find(int va, int vb)
{
     int f1 = top[va], f2 = top[vb], tmp = 0;
     while (f1 != f2)
     {
           if (dep[f1] < dep[f2])
           { swap(f1, f2); swap(va, vb); }
           tmp = max(tmp, maxi(1, 1, z, w[f1], w[va]));
           va = fa[f1]; f1 = top[va];
     }
     if (va == vb) return tmp;
     if (dep[va] > dep[vb]) swap(va, vb);
     return max(tmp, maxi(1, 1, z, w[son[va]], w[vb]));  //
}

void init()
{
     scanf("%d", &n);
     root = (n + 1) / 2;
     fa[root] = z = dep[root] = edge = 0;
     memset(siz, 0, sizeof(siz));
     memset(first, 0, sizeof(first));
     memset(tree, 0, sizeof(tree));
     for (int i = 1; i < n; i++)
     {
         scanf("%d%d%d", &a, &b, &c);
         d[i][0] = a; d[i][1] = b; d[i][2] = c;
         insert(a, b, c);
         insert(b, a, c);
     }
     dfs(root);
     build_tree(root, root);    //
     for (int i = 1; i < n; i++)
     {
         if (dep[d[i][0]] > dep[d[i][1]]) swap(d[i][0], d[i][1]);
         update(1, 1, z, w[d[i][1]], d[i][2]);
     }
}

inline void read()
{
     ch[0] = ‘ ‘;
     while (ch[0] < ‘C‘ || ch[0] > ‘Q‘) scanf("%s", &ch);
}

void work()
{
     for (read(); ch[0] != ‘D‘; read())
     {
         scanf("%d%d", &a, &b);
         if (ch[0] == ‘Q‘) printf("%dn", find(a, b));
                      else update(1, 1, z, w[d[a][1]], b);
     }
}

int main()
{
    for (scanf("%d", &zzz); zzz > 0; zzz--)
    {
        init();
        work();
    }
    return 0;
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.