hdu 6162 Ch’s gift【樹鏈剖分】

第一次敲樹鏈剖分,以前都沒看懂樹鏈剖分用來幹什麼的,爲多校留個紀念。

傳送門:http://acm.hdu.edu.cn/showproblem.php?pid=6162

題意:
給一棵樹,每次問某兩點之間的最短路徑上,能夠滿足價格在[a,b]這個範圍內的價值之和是多少。

其實題意很明顯是個熟練剖分。但是以前都沒有敲過樹鏈剖分,所以並get不到樹剖之後再用樹維護。比賽的時候只意識到這個感覺像個樹套樹。。

順帶講一下樹鏈剖分的知識點。樹鏈剖分就是把一棵樹分成n條線段,然後就可以用線段樹啊,splay,treap之類的東西來維護了。
樹鏈剖分,主要分爲重鏈和輕鏈,重鏈上的節點個數永遠大於輕鏈上的個數。
第一次dfs。記錄每一個節點的兒子個數。
第二次dfs,對於兒子數最多的那個兒子節點,作爲重鏈,其他作爲輕鏈,重新定義一個端點繼續dfs下去。途中還能記錄深度,這條鏈上的點所在的區間等。

附上樹剖代碼:

int size[MAXN];    //兒子節點數 
int fater[MAXN];   //爸爸 
int deep[MAXN];    //深度 
int rak[MAXN];     //離散化樹的節點 
int id[MAXN];      //記錄離散化後的rank對應回的節點 
int son[MAXN];     //樹鏈剖分的重鏈兒子 
int top[MAXN];     //樹鏈剖分的重鏈祖宗 
int rk;

void init() {
    rk = 0;
    memset(son, -1, sizeof(son));
    memset(fater, 0, sizeof(fater));
    memset(size, 0, sizeof(size));
    for (int i = 0; i < MAXN; i++) {
        vec[i].clear();
    }
}

void dfs1(int u, int fa, int dep) {
    deep[u] = dep;
    fater[u] = fa;
    size[u] = 1;
    int len = vec[u].size();
    for (int i = 0; i < len; i++) {
        int v = vec[u][i];
        if (v != fa) {
            dfs1(v, u, dep + 1);
            size[u] += size[v];
            if (son[u] == -1 || size[v] > size[son[u]]) {
                son[u] = v;
            }
        }
    }
}

void dfs2(int u, int tp) {
    top[u] = tp;
    rak[u] = ++rk;
    id[rk] = u;
    if (son[u] == -1) {
        return ;
    } 
    dfs2(son[u], tp);
    int len = vec[u].size();
    for (int i = 0; i < len; i++) {
        int v = vec[u][i];
        if (v != fater[u] && v != son[u]) {
            dfs2(v, v); //輕邊中繼續製造重邊 
        }
    }
}

做完那麼多對樹的操作之後,我們就能用類似LCA的方法,去訪問樹上兩個節點之間的和什麼之類的。
如果兩個點不在同一條鏈上,則讓深度大的那個點,直接訪問到top祖宗節點,那所需要添加的區間爲該點到他祖宗節點的值之和。如果在同一條重鏈上,那就直接查詢rank[x]到rank[y]的區間就好了
這裏是用線段樹維護的,附上樹剖完後查詢的代碼:

ll treeQuery(int x, int y, int n) {
    ll sum = 0;
    int p1 = top[x], p2 = top[y]; //判斷兩個是否在同一重鏈上,看祖宗節點相不相同
    while (p1 != p2) {
        if (deep[p1] < deep[p2]) {
            swap(p1, p2);
            swap(x, y);
        }
        sum += query(rak[p1], rak[x], 1, n, 1);
        x = fater[p1];
        p1 = top[x];
    } 
    if (deep[x] > deep[y]) {
        swap(x, y);
    }
    sum += query(rak[x], rak[y], 1, n, 1);
    return sum;
}

最後我們再來說這道題,官方題解上說,用樹剖後用treap來維護,查詢的時候操作treap。treap不單止有splay的機制,還有堆的機制,可以通過插入刪除節點來查詢到當時插入時的兒子節點區間,然後減一下區間和就好了,這是在線就可以完成的。

而有些題解上說的,樹剖後,在線用線段樹去維護最大值,最小值和區間和,直到找到滿足最大值最小值在所給的[a,b]區間爲止。然而,這樣的時間複雜度是不對的,當成鏈狀時,每次詢問頭和尾,且區間一直最小值大於線段樹上最小值,最大值小於線段樹上最大值,那麼每次都需要跑完整個線段樹(聽說題目太水暴力LCA直接跑都可以過。。)

這裏給出離線處理結果的方法,將所有詢問的左區間排個序,每次插入線段樹的時候,保證左區間的值小於詢問的左區間的值,然後更新所在樹剖鏈上的點,查詢的時候查詢價值所需區間,左邊處理一次,右區間處理一次,然後要答案的時候直接減掉就好了。

代碼有點長,沒有標程那種treap優雅。

/*
@resources: hdu 6162
@date: 2017-08-24
@author: QuanQqqqq
@algorithm: 樹鏈剖分 + segment tree 
*/
#include <bits/stdc++.h>

#define MAXN 100005
#define ll long long
#define lson l, mid, root << 1
#define rson mid + 1, r, root << 1 | 1

using namespace std;

struct node {
    ll l, r;
    int id, x, y;    
};

node val[MAXN], qsn[MAXN];
ll tree[MAXN << 2];
ll ansl[MAXN], ansr[MAXN];
int size[MAXN];    //兒子節點數 
int fater[MAXN];   //爸爸 
int deep[MAXN];    //深度 
int rak[MAXN];     //離散化樹的節點 
int id[MAXN];      //記錄離散化後的rank對應回的節點 
int son[MAXN];     //樹鏈剖分的重鏈兒子 
int top[MAXN];     //樹鏈剖分的重鏈祖宗 
int rk;

vector<int> vec[MAXN];

void addEdge(int u, int v) {
    vec[u].push_back(v);
    vec[v].push_back(u);    
}

void init() {
    rk = 0;
    memset(son, -1, sizeof(son));
    memset(fater, 0, sizeof(fater));
    memset(size, 0, sizeof(size));
    for (int i = 0; i < MAXN; i++) {
        vec[i].clear();
    }
}

int cmpl(node a, node b) {
    return a.l < b.l;
}

int cmpr(node a, node b) {
    return a.r < b.r;
}

void dfs1(int u, int fa, int dep) {
    deep[u] = dep;
    fater[u] = fa;
    size[u] = 1;
    int len = vec[u].size();
    for (int i = 0; i < len; i++) {
        int v = vec[u][i];
        if (v != fa) {
            dfs1(v, u, dep + 1);
            size[u] += size[v];
            if (son[u] == -1 || size[v] > size[son[u]]) {
                son[u] = v;
            }
        }
    }
}

void dfs2(int u, int tp) {
    top[u] = tp;
    rak[u] = ++rk;
    id[rk] = u;
    if (son[u] == -1) {
        return ;
    } 
    dfs2(son[u], tp);
    int len = vec[u].size();
    for (int i = 0; i < len; i++) {
        int v = vec[u][i];
        if (v != fater[u] && v != son[u]) {
            dfs2(v, v); //輕邊中繼續製造重邊 
        }
    }
}

void push_down(int root) {
    tree[root] = tree[root << 1] + tree[root << 1 | 1];    
}

void update(int need, int val, int l, int r, int root) {
    if (l == r) {
        tree[root] += val;
        return ;
    }
    int mid = l + r >> 1;
    if (need <= mid) {
        update(need, val, lson);
    } else {
        update(need, val, rson);
    }
    push_down(root);    
}

ll query(int L, int R, int l, int r, int root) {
    if (L <= l && r <= R) {
        return tree[root];
    }
    int mid = l + r >> 1;
    ll sum = 0;
    if (mid >= L) {
        sum += query(L, R, lson);
    }
    if (mid < R) {
        sum += query(L, R, rson);
    }
    return sum;
}

ll treeQuery(int x, int y, int n) {
    ll sum = 0;
    int p1 = top[x], p2 = top[y]; //判斷兩個是否在同一重鏈上的 
    while (p1 != p2) {
        if (deep[p1] < deep[p2]) {
            swap(p1, p2);
            swap(x, y);
        }
        sum += query(rak[p1], rak[x], 1, n, 1);
        x = fater[p1];
        p1 = top[x];
    } 
    if (deep[x] > deep[y]) {
        swap(x, y);
    }
    sum += query(rak[x], rak[y], 1, n, 1);
    return sum;
}

int main() {
    int n, q, u, v;
    while (~scanf("%d %d", &n, &q)) {
        init();
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &val[i].r);
            val[i].id = i;
        }
        for (int i = 1; i <= n - 1; i++) {
            scanf("%d %d", &u, &v);
            addEdge(u, v);
        }
        dfs1(1, -1, 1);
        dfs2(1, 1);
        sort(val + 1, val + 1 + n, cmpr);
        for (int i = 1; i <= q; i++) {
            scanf("%d %d %lld %lld", &qsn[i].x, &qsn[i].y, &qsn[i].l, &qsn[i].r);
            qsn[i].id = i;
        }
        memset(tree, 0, sizeof(tree));
        sort(qsn + 1, qsn + q + 1, cmpl);
        int j = 1;
        for (int i = 1; i <= q; i++) {
            while (val[j].r < qsn[i].l && j <= n) {
                update(rak[val[j].id], val[j].r, 1, n, 1);
                j++;
            }
            ansl[qsn[i].id] = treeQuery(qsn[i].x, qsn[i].y, n);
        }
        memset(tree, 0, sizeof(tree));
        sort(qsn + 1, qsn + q + 1, cmpr);
        j = 1;
        for (int i = 1; i <= q; i++) {
            while (val[j].r <= qsn[i].r && j <= n) {
                update(rak[val[j].id], val[j].r, 1, n, 1);
                j++;
            }
            ansr[qsn[i].id] = treeQuery(qsn[i].x, qsn[i].y, n);
        }
        for (int i = 1; i <= q; i++) {
            if (i != 1) {
                printf(" ");
            }
            printf("%lld", ansr[i] - ansl[i]);
        }
        puts("");
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章