1. 程式人生 > 實用技巧 >Luogu3787 冰精凍西瓜(樹上操作)題解

Luogu3787 冰精凍西瓜(樹上操作)題解

這題面不禁讓我聯想到了樹剖

思路

顯然,這道題並不需要樹剖畢竟它只是藍題,不過,它也需要用到\(DFS\)序,把樹上操作轉化成序列操作(這個思想可以說很套路了),但這題有一個麻煩的地方:在修改子樹時每一條邊都會對修改值造成影響,而這用線段樹是難以維護的。

於是我們考慮將邊上的影響分離出來,可以先預處理出一個從當前節點到根的字首積,在每次子樹加時將加數除以當前節點的字首積,查詢時再乘上查詢節點的字首積,我們發現,通過這樣的操作,就將修改節點到根節點的字首積乘回來了。是不是很妙

But

還有一個細節:資料範圍中特意強調了\(w_i\)可能為\(0\),而我們知道,當\(w_i\)為0時,修改其他節點(指除i及其子樹以外的節點)就對i節點及其子樹沒有影響了。為了方便地處理,我們可以巧妙地將這些\(w_i\)

\(0\)的點也設為根,將原樹拆分成幾棵樹遍歷,這樣就可以解決\(0\)的問題且子樹編號仍然連續了。同時,我們每一個點仍然只會遍歷一次(為根節點兩次),所以遍歷仍然是\(O(n)\)級別的。

程式碼

#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn = 2e5 + 10;  //兩倍空間開了嗎
int n,head[maxn],num,m;
int rm[maxn],rt[maxn],top;
double tim[maxn];
struct Edge{
    int then,to;
    double val;
}e[maxn];

void add(int u, int v, double val){e[++num] = (Edge){head[u], v, val}; head[u] = num;}

int dfn[maxn],id[maxn],cnt,siz[maxn];
void DFS(int u, int f){  
    dfn[++cnt] = u, id[u] = cnt; siz[u] = 1;
    for(int i = head[u]; i; i = e[i].then){
        int v = e[i].to;
        if(v != f && !rm[v] && !id[v]){
            if(fabs(e[i].val - 0.0) <= 0.00000001) rm[v] = 1, rt[++top] = v, tim[v] = 1;  //注意double判0的時候儘量別用==
            else tim[v] = tim[u] * e[i].val, DFS(v, u), siz[u] += siz[v];
        }
    }
}

struct Seg_Tree{
    #define lc(x) x << 1
    #define rc(x) x << 1 | 1
    double c[maxn << 2],tag[maxn << 2];
    
    void f(int l, int r, int p, double x){
        c[p] += (r - l + 1) * x;
        tag[p] += x;
    }
    
    void downdate(int l, int r, int p){
        if(fabs(tag[p] - 0.0) >= 0.00000001){
            int mid = (l + r) >> 1;
            f(l, mid, lc(p), tag[p]);
            f(mid + 1, r, rc(p), tag[p]);
            tag[p] = 0;
        }
    }
    
    void add(int L, int R, int l, int r, int p, double val){
        if(L <= l && R >= r){
            f(l, r, p, val);
            return;
        }
        downdate(l, r, p);
        int mid = (l + r) >> 1;
        if(L <= mid) add(L, R, l, mid, lc(p), val);
        if(mid < R) add(L, R, mid + 1, r, rc(p), val);
        c[p] = c[lc(p)] + c[rc(p)];
    }
    
    double query(int pos, int l, int r, int p){
        if(l == r) return c[p];
        downdate(l, r, p);
        int mid = (l + r) >> 1; 
        if(mid >= pos) return query(pos, l, mid, lc(p));
        else return query(pos, mid + 1, r, rc(p));
    }
}tree;

void push(int u, double val){
    val /= tim[u];
    tree.add(id[u], id[u] + siz[u] - 1, 1, cnt, 1, val);
}

double get_ans(int u){
    double ans = tree.query(id[u], 1, cnt, 1);
    return ans * tim[u];
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i < n; ++ i){
        int u,v; double val;
        scanf("%d%d%lf", &u, &v, &val);
        add(u, v, val), add(v, u, val);
    } tim[1] = 1, DFS(1, 0);
    for(int i = 1; i <= top; ++ i) DFS(rt[i], 0);
    scanf("%d", &m);
    while(m--){
        int opt,Id; double x;
        scanf("%d%d", &opt, &Id);
        if(opt == 1){
            scanf("%lf", &x);
            push(Id, x);
        }
        else printf("%.8lf\n", get_ans(Id));
    }
    return 0;
}