1. 程式人生 > 實用技巧 >java提高篇(三)-----理解java的三大特性之多型

java提高篇(三)-----理解java的三大特性之多型

目錄

樹鏈剖分

1.演算法分析

dfs1函式:這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0

1.1 重鏈剖分:

1.1.1 定義

    重子節點 表示其子節點中子樹最大的子結點。如果有多個子樹最大的子結點,取其一。如果沒有子節點,就無重子節點。輕子節點

表示剩餘的所有子結點。從這個結點到重子節點的邊為 重邊 。到其他輕子節點的邊為 輕邊 。若干條首尾銜接的重邊構成 重鏈 。把落單的結點也當作重鏈,那麼整棵樹就被剖分成若干條重鏈。

1.1.2 重鏈剖分的性質

    樹上每個節點都屬於且僅屬於一條重鏈。 重鏈開頭的結點不一定是重子節點(因為重邊是對於每一個結點都有定義的)。所有的重鏈將整棵樹 完全剖分 。在剖分時 優先遍歷重兒子 ,最後重鏈的DFS序就會是連續的。在剖分時,重邊優先遍歷,最後樹的 DFN 序上,重鏈內的DFN序是連續的。按DFN排序後的序列即為剖分後的鏈。一顆子樹內的 DFN 序是連續的。可以發現,當我們向下經過一條 輕邊 時,所在子樹的大小至少會除以二。因此,對於樹上的任意一條路徑,把它拆分成從 分別向兩邊往下走,分別最多走O(logn)次,因此,樹上的每條路徑都可以被拆分成不超過 O(logn)

條重鏈。

1.1.3 常見應用

  1. 路徑維護:用樹鏈剖分求樹上兩點路徑權值和。鏈上的 DFS 序是連續的,可以使用線段樹、樹狀陣列維護。每次選擇深度較大的鏈往上跳,直到兩點在同一條鏈上。同樣的跳鏈結構適用於維護、統計路徑上的其他資訊。
  2. 子樹維護:維護子樹上的資訊,譬如將以x為根的子樹的所有結點的權值增加v。在 DFS 搜尋的時候,子樹中的結點的 DFS 序是連續的。每一個結點記錄 bottom 表示所在子樹連續區間末端的結點。這樣就把子樹資訊轉化為連續的一段區間資訊。
  3. 求最近公共祖先:不斷向上跳重鏈,當跳到同一條重鏈上時,深度較小的結點即為 LCA。

1.2 長鏈剖分

1.2.1 定義

長鏈剖分本質上就是另外一種鏈剖分方式。
定義 重子節點

表示其子節點中子樹深度最大的子結點。如果有多個子樹最大的子結點,取其一。如果沒有子節點,就無重子節點。
定義 輕子節點 表示剩餘的子結點。
從這個結點到重子節點的邊為 重邊
到其他輕子節點的邊為 輕邊
若干條首尾銜接的重邊構成 重鏈
把落單的結點也當作重鏈,那麼整棵樹就被剖分成若干條重鏈。
如圖(這種剖分方式既可以看成重鏈剖分也可以看成長鏈剖分):

1.2.2 長鏈剖分性質

  1. 任意點祖先所在長鏈長度一點大於等於這個點所在長鏈長度
  2. 所有長鏈長度之和就是總結點數
  3. 一個點到根節點的路徑上經過的短邊最多有 √n 條 (長兒子深度和短兒子深度相等時取到)

1.2.3 長鏈剖分應用

  1. O(1) 移動到鏈頭 (求lca,和重鏈剖分一樣)
  2. O(nlogn) 預處理,單次 O(1) 線上查詢一個點的 k 級祖先
  3. O(n) 處理可合併的與深度有關的子樹資訊 (例如某深度點數、某深度點權和)

2. 板子

2.1 重鏈剖分

2.1.1 路徑維護/子樹維護/lca

luogu P3384 【模板】輕重鏈剖分
    一棵包含 N 個結點的樹(連通且無環),每個節點上包含一個數值,需要支援以下操作:
操作 1: 格式: 1 x y z 表示將樹從 x 到 y 結點最短路徑上所有節點的值都加上 z。
操作 2: 格式: 2 x y 表示求樹從 x 到 y 結點最短路徑上所有節點的值之和。
操作 3: 格式: 3 x z 表示將以 x 為根節點的子樹內所有節點值都加上 z。
操作 4: 格式: 4 x 表示求以 x 為根節點的子樹內所有節點值之和
操作有1e5個,樹的節點數為1e5個

#include<bits/stdc++.h>
using namespace std;

const int N=100000+10;

int tot, num;
int n, m, r, p;

int w[N], a[N], dat[N*4], lazy[N*4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N*2], ne[N*2], idx;   // 鄰接表陣列
int dep[N], dfn[N], wson[N], size[N], top[N], fa[N]; //  dep深度   dfn搜尋序 wson重兒子 size子樹大小 top鏈頭  fa父節點

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到size, fa, dep, wson陣列
void dfs1(int u)
{
    dep[u]=dep[fa[u]]+1; 
    size[u]=1; 
    for(int i=h[u]; ~i; i=ne[i])
    {#include<bits/stdc++.h>
using namespace std;

const int N = 100000+10;

int tot, num;
int n, m, r, p;

int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N * 2], ne[N * 2], idx;   // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜尋序 wson重兒子 size子樹大小 top鏈頭  fa父節點

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
    dep[u] = dep[fa[u]]+1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j=e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
    }
}

// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜尋序重排權值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜尋重兒子
    for(int i = h[u]; ~i; i = ne[i])  // 然後搜尋輕兒子
    {
        int y=e[i]; 
        if(y ==fa[u]||y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
    dat[rt] %= p; 
}

// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
    if(l==r)
    {
        dat[rt]=w[l]; 
        return ; 
    }
    int mid=(l + r)>>1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

// 下傳
void pushdown(int rt, int l, int r)
{
    if(lazy[rt])
    {
        int mid=(l + r)>>1; 
        dat[rt << 1] += lazy[rt]*(mid - l + 1), dat[rt << 1] %= p, lazy[rt << 1] += lazy[rt]; 
        dat[rt << 1 | 1] += lazy[rt]*(r-mid), dat[rt << 1 | 1] %= p, lazy[rt << 1 | 1] += lazy[rt]; 
        lazy[rt]=0; 
    }
}

// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要修改的左區間,r為需要修改的右區間
void modify(int rt, int l, int r, int L, int R, int k)
{
    if(L <= l && r <= R)
    {
        dat[rt] += k*(r-l+1); 
        dat[rt] %= p; 
        lazy[rt] += k; 
        lazy[rt] %= p; 
        return ; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    if(L <= mid) modify(rt << 1, l, mid, L, R, k); 
    if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k); 
    pushup(rt);
}

// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要查詢的左區間,r為查詢的右區間
int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R)
    {
        return dat[rt]; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p; 
    if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p; 
    return ans; 
}

// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree(int x, int y)
{
    //兩點間的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 讓x點為深的那個點
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        ans %= p; 
        x = fa[top[x]];  // x每次跳一條鏈
    }
    if(dep[x] > dep[y]) swap(x, y);   // 讓x成為深度更淺的那個點
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans % p; 
}

// 將樹從 x到 y 結點最短路徑上所有節點的值都加上 z
void path_modify_Tree(int x, int y, int k)
{
    //樹上兩點距離
    k %= p; 
    while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x成為對應的頭部深度更大的那個點
        modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子樹和
        x = fa[top[x]]; // x跳到原來x的頭部的父節點
    }
    if(dep[x] > dep[y]) swap(x, y);  // 讓x成為深度更淺的那個點
    modify(1, 1, n, dfn[x], dfn[y], k); 
}

// 求以 x 為根節點的子樹內所有節點值之和
int Point_query_Tree(int rt)
{
    //由搜尋序的特點可得,子樹的搜尋序一定比根大
    return query(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1); 
}

// 將以 x 為根節點的子樹內所有節點值都加上 z
void Point_modify_Tree(int rt, int k)
{
    k %= p; 
    modify(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1, k); 
}

// 求u和v的lca
int lca(int u, int v) 
{
    while (top[u] != top[v]) 
    {
        if (dep[top[u]] > dep[top[v]])
            u = fa[top[u]];
        else
            v = fa[top[v]];
    }
    return dep[u] > dep[v] ? v : u;
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &r, &p);  // 讀入點數、邊數、根、模數

    for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 讀入每個點的權值

    // 讀入邊,建樹
    memset(h,  -1,  sizeof h); 
    for(int i=1, x, y; i<n; i++)
    {
        scanf("%d%d", &x, &y); 
        add(x, y); 
        add(y, x); 
    }

    // 兩次dfs把樹按照重鏈剖分
    dfs1(r);   // 得到sz, fa, dep, wson陣列
    dfs2(r, r);   // 得到dfn, top陣列
    build(1, 1, n); 

    // m次詢問
    for(int i=1, op, x, y, z; i<=m; i++)
    {
        scanf("%d", &op); 
        if(op == 1)
        {
            scanf("%d%d%d", &x, &y, &z); 
            path_modify_Tree(x, y, z); // 將樹從 x到 y 結點最短路徑上所有節點的值都加上 z。
        }
        else if(op == 2)
        {
            scanf("%d%d", &x, &y); 
            printf("%d\n", path_query_Tree(x, y)); //求樹從 x 到 y 結點最短路徑上所有節點的值之和
        }
        else if(op == 3)
        {
            scanf("%d%d", &x, &z); 
            Point_modify_Tree(x, z);  // 將以 x 為根節點的子樹內所有節點值都加上 z
        }
        else 
        {
            scanf("%d", &x); 
            printf("%d\n", Point_query_Tree(x)); //求以 x 為根節點的子樹內所有節點值之和
        }
    }

    return 0;
}

lugou P2486 [SDOI2011]染色
題意:
給定一棵 n 個節點的無根樹,共有 m 個操作,操作分為兩種:

  • 將節點 aa 到節點 bb 的路徑上的所有點(包括 aa 和 bb)都染成顏色 cc。
  • 詢問節點 aa 到節點 bb 的路徑上的顏色段數量。

顏色段的定義是極長的連續相同顏色被認為是一段。例如 112221 由三段組成:11、222、1。
題解: 線段樹+樹剖,線段樹維護顏色資訊即可
以下程式碼沒有調通

#include<bits/stdc++.h>
using namespace std;

const int N=1e5 + 10;

int tot, num;
int n, m, r;

int w[N], a[N], lco[N * 4], rco[N * 4], dat[N*4], lazy[N*4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N*2], ne[N*2], idx;   // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜尋序 wson重兒子 sz子樹大小 top鏈頭  fa父節點

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
    dep[u] = dep[fa[u]] + 1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u]=j;  // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
    }
}

// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜尋序重排權值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜尋重兒子
    for(int i = h[u]; ~i; i = ne[i])  // 然後搜尋輕兒子
    {
        int y = e[i]; 
        if(y == fa[u] || y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    lco[rt] = lco[rt << 1] , rco[rt] = lco[rt << 1 | 1];
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
    if (rco[rt << 1] == lco[rt << 1 | 1]) dat[rt]--;
}

// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
    if(l == r)
    {
        dat[rt] = 1; 
        lco[rt] = rco[rt] = w[l];
        lazy[rt] = 0;
        return ; 
    }
    int mid = (l + r) >> 1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid + 1, r); 
    pushup(rt);
}

void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {
        lazy[rt << 1] = lazy[rt];
        lazy[rt << 1 | 1] = lazy[rt];
        
        lco[rt << 1] = rco[rt << 1] = lazy[rt];
        lco[rt << 1 | 1] = rco[rt << 1 | 1] = lazy[rt];
        
        dat[rt << 1] = 1;
        dat[rt << 1 | 1] = 1;
        lazy[rt] = 0;
    } 
}

void modify(int rt, int l, int r, int L, int R, int c) {
    if (L <= l && r <= R) {
        dat[rt] = 1;
        lco[rt] = rco[rt] = c;
        lazy[rt] = c;
        return;
    }
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, c);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, c);
    pushup(rt);
}

int query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    int res = 0, flg = 0;
    if (L <= mid) {
        res = query(rt << 1, l, mid, L, R), flg = 1;
    }
    if (mid < R) {
        res += query(rt << 1 | 1, mid + 1, r, L, R);
        if (flg == 1 && rco[rt << 1] == lco[rt << 1 | 1]) res--;
    }
    return res;
}

// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree(int x, int y)
{
    //兩點間的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 讓x點為深的那個點
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        if (a[top[x]] == a[fa[top[x]]]) ans--;
        x = fa[top[x]];  // x每次跳一條鏈
    }
    if(dep[x] > dep[y]) swap(x, y);   // 讓x成為深度更淺的那個點
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans; 
}

// 將樹從 x到 y 結點最短路徑上所有節點的值都加上 z
void path_modify_Tree(int x, int y, int k)
{
    //樹上兩點距離
    while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x成為對應的頭部深度更大的那個點
        modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子樹和
        x = fa[top[x]]; // x跳到原來x的頭部的父節點
    }
    if(dep[x] > dep[y]) swap(x, y);  // 讓x成為深度更淺的那個點
    modify(1, 1, n, dfn[x], dfn[y], k); 
}

int main()
{
    scanf("%d%d", &n, &m);  // 讀入點數、邊數、根、模數
    r = 1;

    for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 讀入每個點的權值

    // 讀入邊,建樹
    memset(h,  -1,  sizeof h); 
    for(int i = 1, x, y; i < n; i++)
    {
        scanf("%d%d", &x, &y); 
        add(x, y); 
        add(y, x); 
    }

    // 兩次dfs把樹按照重鏈剖分
    dfs1(r);   // 得到sz, fa, dep, wson陣列
    dfs2(r, r);   // 得到dfn, top陣列
    build(1, 1, n); 
    
    // m次詢問
    string op;
    for(int i = 1, x, y, z; i <= m; i++)
    {
        cin >> op;
        if (op[0] == 'Q') {
            scanf("%d %d", &x, &y);
            printf("%d\n", path_query_Tree(x, y));
        }
        else {
            scanf("%d%d%d", &x, &y, &z);
            path_modify_Tree(x, y, z);
        }
    }
    return 0;
}

2.2 長鏈剖分

2.2.1 求lca/求每條長鏈的長度

求lca

#include<bits/stdc++.h>
using namespace std;

const int N=500000+10;

int lson[N], maxlen[N], fa[N], dep[N], top[N]; // lso長兒子,maxlen最大深度,fa父節點,dep深度,top鏈頭
int h[N],e[N*2],ne[N*2], idx;
int n, m, root;

void add(int a,int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 求fa,dep, maxlen
void dfs1(int x)
{
    dep[x]=dep[fa[x]]+1;
    maxlen[x]=dep[x]; 
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x])
        {
            fa[y]=x;
            dfs1(y);
            maxlen[x]=max(maxlen[y],maxlen[x]);
            if(maxlen[y]>maxlen[lson[x]]) lson[x]=y;  // 這裡要注意根節點不能設為0,否則根節點的最長鏈無法更新,始終為0
        }
    }
}

// 求top
void dfs2(int x,int nowtop)
{
    top[x]=nowtop;
    if(lson[x]) dfs2(lson[x],nowtop);
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x]&&y!=lson[x]) dfs2(y,y);
    }
}

// 求lca
int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}

int main()
{
    scanf("%d%d%d",&n,&m,&root);
    memset(h, -1, sizeof h);
    for(int i=1,x,y;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }

    dfs1(root);  // 第一次求dep, fa, maxlen, lson
    dfs2(root,root);  // 第二次求top

    for(int i=1,x,y;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }

    return 0;
}

Benelux Algorithm Programming Contest 2019
A. Appeal to the Audience

題意: 給定一棵n點樹,該樹有m葉,每片葉子都有權值k,每片葉子能帶來葉子所處的長鏈的長度*葉子權值k的收益,求最大收益。

題解: 長鏈的貪心性質
程式碼:

/*
本題需要讓越大權值的運動員位於越長的鏈的底部,即可求出最大答案
只需要多維護一個數組len,記錄每條鏈的長度即可
*/

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=100000+10;

int lson[N], maxlen[N], fa[N], dep[N], top[N]; // lso長兒子,maxlen最大深度,fa父節點,dep深度,top鏈頭
int h[N],e[N*2],ne[N*2], idx;
int n, m, root;
int a[N], len[N];  // len維護每一條鏈的長度

void add(int a,int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 求fa,dep, maxlen
void dfs1(int x)
{
    dep[x]=dep[fa[x]]+1;
    maxlen[x]=dep[x]; 
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x])
        {
            fa[y]=x;
            dfs1(y);
            maxlen[x]=max(maxlen[y],maxlen[x]);
            if(maxlen[y]>maxlen[lson[x]]) lson[x]=y;
        }
    }
}

// 求top
void dfs2(int x,int nowtop, int length)
{
    top[x]=nowtop;
	len[nowtop] = length + 1;  // 鏈頭為nowtop的鏈的長度為length+1
    if(lson[x]) dfs2(lson[x],nowtop, len[nowtop]);
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x]&&y!=lson[x]) dfs2(y,y, 0);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) scanf("%d", &a[i]);
    sort(a + 1, a + 1 + m);
    reverse(a + 1, a + 1 + m);
    memset(h, -1, sizeof h);
    for(int i=2, x;i<=n;++i)
    {
        cin >> x;
        x++;
        add(i, x), add(x, i);
    }

    root = 1;
    dfs1(root);  // 第一次求dep, fa, maxlen, lson
    dfs2(root,root, 0);  // 第二次求top

    set<int> s;
    for (int i = 1; i <= n; ++i) s.insert(top[i]);
    LL ans = 0;
    int i = 0;
    vector<int> v;
    for (auto si: s) v.push_back(len[si]);
    sort(v.begin(), v.end());
    reverse(v.begin(), v.end());
    for (int i = 0; i < m; ++i) 
    {
        if (i == 0) v[i]--;
        ans += a[i + 1] * (LL)(v[i]);
    }
    cout << ans << endl;

    return 0;
}

2.2.2 查詢一個點的k級祖先

2.2.3 O(n)處理可合併的與深度有關的子樹資訊

3. 例題

3.1 重鏈剖分

P2590 [ZJOI2008]樹的統計
題意:
一棵樹上有 n 個節點,編號分別為 1 到 n,每個節點都有一個權值 w。
我們將以下面的形式來要求你對這棵樹完成一些操作:
I. CHANGE u t : 把結點 u 的權值改為 t。
II. QMAX u v: 詢問從點 u 到點 v 的路徑上的節點的最大權值。
III. QSUM u v: 詢問從點 u 到點 v 的路徑上的節點的權值和。
注意:從點 u 到點 v 的路徑上的節點包括 u 和 v 本身。
題解:
只需要線段樹加單點修改區間查詢的線段樹即可,模板題
程式碼:

#include<bits/stdc++.h>
using namespace std;

const int N = 3e4 + 10, INF = 1e9 + 10;

int tot, num;
int n, m, r;

// dat1:sum, dat2:max
int w[N], a[N], dat1[N * 4], dat2[N * 4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N * 2], ne[N * 2], idx;   // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜尋序 wson重兒子 size子樹大小 top鏈頭  fa父節點

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
    dep[u] = dep[fa[u]] + 1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j=e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
    }
}

// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜尋序重排權值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜尋重兒子
    for(int i = h[u]; ~i; i = ne[i])  // 然後搜尋輕兒子
    {
        int y = e[i]; 
        if(y == fa[u] || y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    dat1[rt] = dat1[rt << 1] + dat1[rt << 1 | 1]; 
    dat2[rt] = max(dat2[rt << 1], dat2[rt << 1 | 1]);
}

// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
    if(l == r)
    {
        dat1[rt] = dat2[rt] = w[l]; 
        return ; 
    }
    int mid = (l + r) >> 1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

void modify(int rt, int l, int r, int x, int y) {
    if (l == r) {
        dat1[rt] = y;
        dat2[rt] = y;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) modify(rt << 1, l, mid, x, y);
    else modify(rt << 1 | 1, mid + 1, r, x, y);
    pushup(rt);
}

int query1(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return dat1[rt]; 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query1(rt << 1, l, mid, L, R); 
    if(mid < R) ans += query1(rt << 1 | 1, mid + 1, r, L, R); 
    return ans; 
}

int query2(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat2[rt];
    int mid = (l + r) >> 1;
    int ans = -INF;
    if (L <= mid) ans = max(ans, query2(rt << 1, l, mid, L, R));
    if (mid < R) ans = max(ans, query2(rt << 1 | 1, mid + 1, r, L, R));
    return ans;
}

// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree1(int x, int y)
{
    //兩點間的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 讓x點為深的那個點
        ans += query1(1, 1, n, dfn[top[x]], dfn[x]);   
        x = fa[top[x]];  // x每次跳一條鏈
    }
    if(dep[x] > dep[y]) swap(x, y);   // 讓x成為深度更淺的那個點
    ans += query1(1, 1, n, dfn[x], dfn[y]); 
    return ans; 
}

// 求樹從 x 到 y 結點最短路徑上所有節點的max
int path_query_Tree2(int x, int y)
{
    //兩點間的修改
    int ans = -INF; 
    while(top[x] != top[y])  // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 讓x點為深的那個點
        ans = max(ans, query2(1, 1, n, dfn[top[x]], dfn[x]));   
        x = fa[top[x]];  // x每次跳一條鏈
    }
    if(dep[x] > dep[y]) swap(x, y);   // 讓x成為深度更淺的那個點
    ans = max(ans, query2(1, 1, n, dfn[x], dfn[y])); 
    return ans; 
}

void modify_Tree(int x, int y) {
    modify(1, 1, n, dfn[x], y);
}

int main()
{
    scanf("%d", &n);  // 讀入點數、邊數、根、模數
    r = 1;

    // 讀入邊,建樹
    memset(h,  -1,  sizeof h); 
    for(int i = 1, x, y; i < n; i++)
    {
        scanf("%d%d", &x, &y); 
        add(x, y); 
        add(y, x); 
    }

    for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 讀入每個點的權值
    
    scanf("%d", &m);
    
    // 兩次dfs把樹按照重鏈剖分
    dfs1(r);   // 得到sz, fa, dep, wson陣列
    dfs2(r, r);   // 得到dfn, top陣列
    build(1, 1, n); 

    // m次詢問
    for(int i=1, u, v; i<=m; i++)
    {
        char op[10];
        scanf("%s", op);
        scanf("%d%d", &u, &v);
        if (op[0] == 'Q' && op[1] == 'S') printf("%d\n", path_query_Tree1(u, v));
        else if (op[0] == 'Q' && op[1] == 'M') printf("%d\n", path_query_Tree2(u, v));
        else modify_Tree(u, v);
    }

    return 0;
}

P2146 [NOI2015]軟體包管理器
題意:
    你決定設計你自己的軟體包管理器。不可避免地,你要解決軟體包之間的依賴問題。如果軟體包 a 依賴軟體包 b,那麼安裝軟體包 a 以前,必須先安裝軟體包 b。同時,如果想要解除安裝軟體包 b,則必須解除安裝軟體包 a。
    現在你已經獲得了所有的軟體包之間的依賴關係。而且,由於你之前的工作,除 0 號軟體包以外,在你的管理器當中的軟體包都會依賴一個且僅一個軟體包,而 0 號軟體包不依賴任何一個軟體包。且依賴關係不存在環
    現在你要為你的軟體包管理器寫一個依賴解決程式。根據反饋,使用者希望在安裝和解除安裝某個軟體包時,快速地知道這個操作實際上會改變多少個軟體包的安裝狀態(即安裝操作會安裝多少個未安裝的軟體包,或解除安裝操作會解除安裝多少個已安裝的軟體包),你的任務就是實現這個部分。
    注意,安裝一個已安裝的軟體包,或解除安裝一個未安裝的軟體包,都不會改變任何軟體包的安裝狀態,即在此情況下,改變安裝狀態的軟體包數為 0。
題解: 通過分析可以知道軟體依賴關係是一棵樹,當安裝軟體時,就是影響從根(0節點)到x;當解除安裝軟體時,就是影響以x為根的子樹。所以只需要線段樹lazy維護區間染色,dat維護區間和即可。
程式碼:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int tot, num;
int n, m, r;

int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N * 2], ne[N * 2], idx;   // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜尋序 wson重兒子 size子樹大小 top鏈頭  fa父節點

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
    dep[u] = dep[fa[u]]+1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j=e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
    }
}

// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜尋序重排權值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜尋重兒子
    for(int i = h[u]; ~i; i = ne[i])  // 然後搜尋輕兒子
    {
        int y=e[i]; 
        if(y ==fa[u]||y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
    if(l==r)
    {
        dat[rt] = 0; 
        lazy[rt] = -1;
        return ; 
    }
    int mid=(l + r)>>1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

// 下傳
void pushdown(int rt, int l, int r)
{
    if (lazy[rt] == -1) return;
    int mid=(l + r)>>1; 
    dat[rt << 1] = lazy[rt]*(mid - l + 1), lazy[rt << 1] = lazy[rt]; 
    dat[rt << 1 | 1] = lazy[rt]*(r-mid), lazy[rt << 1 | 1] = lazy[rt]; 
    lazy[rt]=-1; 
}

// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要修改的左區間,r為需要修改的右區間
void modify(int rt, int l, int r, int L, int R, int k)
{
    if(L <= l && r <= R)
    {
        dat[rt] = k*(r-l+1); 
        lazy[rt] = k; 
        return ; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    if(L <= mid) modify(rt << 1, l, mid, L, R, k); 
    if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k); 
    pushup(rt);
}

// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要查詢的左區間,r為查詢的右區間
int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R)
    {
        return dat[rt]; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query(rt << 1, l, mid, L, R); 
    if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R); 
    return ans; 
}

// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree(int x, int y)
{
    //兩點間的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 讓x點為深的那個點
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        x = fa[top[x]];  // x每次跳一條鏈
    }
    if(dep[x] > dep[y]) swap(x, y);   // 讓x成為深度更淺的那個點
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans; 
}

// 將樹從 x到 y 結點最短路徑上所有節點的值都賦值為 z
void path_modify_Tree(int x, int y, int k)
{
    //樹上兩點距離
    while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x成為對應的頭部深度更大的那個點
        modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子樹和
        x = fa[top[x]]; // x跳到原來x的頭部的父節點
    }
    if(dep[x] > dep[y]) swap(x, y);  // 讓x成為深度更淺的那個點
    modify(1, 1, n, dfn[x], dfn[y], k); 
}

// 求以 x 為根節點的子樹內所有節點值之和
int Point_query_Tree(int rt)
{
    //由搜尋序的特點可得,子樹的搜尋序一定比根大
    return query(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1); 
}

void Point_modify_Tree(int rt, int k)
{
    modify(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1, k); 
}

int main()
{
    scanf("%d", &n);
    r = 1;
    // 讀入邊,建樹
    memset(h,  -1,  sizeof h); 
    for (int i = 2, x; i <= n; ++i) {
        scanf("%d", &x);
        x++;
        add(i, x), add(x, i);
    }

    scanf("%d", &m);

    // 兩次dfs把樹按照重鏈剖分
    dfs1(r);   // 得到sz, fa, dep, wson陣列
    dfs2(r, r);   // 得到dfn, top陣列
    build(1, 1, n); 

    // m次詢問
    char op[10];
    for(int i=1, x; i<=m; i++)
    {
        scanf("%s", op);
        scanf("%d", &x);
        x++;
        if(op[0] == 'i')
        {
            int cnt1 = path_query_Tree(1, x);
            path_modify_Tree(1, x, 1); 
            printf("%d\n", path_query_Tree(1, x) - cnt1);
        }
        else 
        {
            int cnt1 = Point_query_Tree(x);
            Point_modify_Tree(x, 0);
            printf("%d\n", cnt1); //求樹從 x 到 y 結點最短路徑上所有節點的值之和
        }
    }
    return 0;
}