[ZJOI2015] 地震後的幻想鄉
阿新 • • 發佈:2022-03-21
前言
點分樹練習題。
題目
講解
我們發現這道題相當於找樹上帶權重心。
有一個我認為挺nb的貪心是從根開始走,每次走最優的那個兒子,走不動了就是答案,而且對於每個當前點,至多隻會有一個兒子比它優。
只考慮走的話,複雜度是 \(O(depth)\) 的,考慮優化它,理所當然會想到點分樹,再結合每個點度數不超過 20 這個條件,做法基本就明晰了。
從點分樹的根開始,在原樹上找最優的兒子,走點分樹上的邊。
現在問題是怎麼算花費,其實我們每次修改的時候在點分樹上改,查詢也可以在點分樹上查,記錄到點分樹上的點的父親的花費,把這個花費在父親和該點都記錄一下,就可以實現 \(O(\log_2n)\)
為了保證複雜度需要 \(O(1)\) LCA,總複雜度是 \(O(20n\log_2^2n)\)。
程式碼
//12252024832524 #include <bits/stdc++.h> #define TT template<typename T> using namespace std; typedef long long LL; const int MAXN = 200005; int n,Q; LL Read() { LL x = 0,f = 1;char c = getchar(); while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();} return x * f; } TT void Put1(T x) { if(x > 9) Put1(x/10); putchar(x%10^48); } TT void Put(T x,char c = -1) { if(x < 0) putchar('-'),x = -x; Put1(x); if(c >= 0) putchar(c); } TT T Max(T x,T y){return x > y ? x : y;} TT T Min(T x,T y){return x < y ? x : y;} TT T Abs(T x){return x < 0 ? -x : x;} int hd[MAXN],head[MAXN],tot; struct edge{ int v,w,nxt; }e[MAXN<<2]; void Add_Edge(int u,int v,int w){ e[++tot] = edge{v,w,head[u]}; head[u] = tot; } void Add_Double_Edge(int u,int v,int w){ Add_Edge(u,v,w); Add_Edge(v,u,w); } int dfn[MAXN],dfntot,st[MAXN][19],d[MAXN],who[MAXN],LG[MAXN]; int up(int u,int v){return d[u] < d[v] ? u : v;} int lca(int u,int v){ u = who[u]; v = who[v]; if(u > v) swap(u,v); int k = LG[v-u+1]; return up(st[u][k],st[v-(1<<k)+1][k]); } int dis(int u,int v){if(!u) return 0;return d[u] + d[v] - (d[lca(u,v)] << 1);} void dfs1(int x,int fa){ st[who[x] = ++dfntot][0] = x; for(int i = head[x],v; i ;i = e[i].nxt){ if((v = e[i].v) == fa) continue; d[v] = d[x] + e[i].w; dfs1(v,x); st[++dfntot][0] = x; } } int rt,siz[MAXN],MAX[MAXN],RT; bool vis[MAXN]; void getrt(int x,int fa,int S){ siz[x] = 1; MAX[x] = 0; for(int i = head[x],v; i ;i = e[i].nxt){ if(vis[v = e[i].v] || v == fa) continue; getrt(v,x,S); siz[x] += siz[v]; MAX[x] = Max(MAX[x],siz[v]); } MAX[x] = Max(MAX[x],S-siz[x]); if(!rt || MAX[x] < MAX[rt]) rt = x; } void dfs3(int x,int fa){ siz[x] = 1; for(int i = head[x],v; i ;i = e[i].nxt){ if(vis[v = e[i].v] || v == fa) continue; dfs3(v,x); siz[x] += siz[v]; } } int f[MAXN]; void dfs2(int x){ vis[x] = 1; dfs3(x,0); for(int i = head[x],v; i ;i = e[i].nxt){ e[i].w = 0; if(vis[v = e[i].v]) continue; rt = 0; getrt(v,x,siz[v]); f[rt] = x; e[i].w = rt; dfs2(rt); } } LL sd[MAXN],dis1[MAXN],dis2[MAXN]; LL calc(int x){ LL ret = dis1[x]; for(int i = x,dd; i ;i = f[i]){ dd = dis(f[i],x); ret += dis1[f[i]] - dis2[i]; ret += 1ll * dd * (sd[f[i]] - sd[i]); } return ret; } LL Query(int x){ LL ret = calc(x); for(int i = head[x]; i ;i = e[i].nxt){ if(!e[i].w) continue; LL tmp = calc(e[i].v); if(tmp < ret) return Query(e[i].w); } return ret; } int main() { // freopen("tree.in","r",stdin); // freopen("tree.out","w",stdout); n = Read(); Q = Read(); for(int i = 1,u,v;i < n;++ i) u = Read(),v = Read(),Add_Double_Edge(u,v,Read()); dfs1(1,0); LG[0] = -1; for(int i = 1;i <= dfntot;++ i) LG[i] = LG[i>>1] + 1; for(int i = dfntot;i >= 1;-- i) for(int j = 1;i+(1<<j)-1 <= dfntot;++ j) st[i][j] = up(st[i][j-1],st[i+(1<<(j-1))][j-1]); getrt(1,0,n); dfs2(RT = rt); while(Q --> 0){ int x = Read(),val = Read(); sd[x] += val; for(int i = x,dd; i ;i = f[i]){ dd = dis(f[i],x); dis1[f[i]] += 1ll * dd * val; dis2[i] += 1ll * dd * val; sd[f[i]] += val; } Put(Query(RT),'\n'); } return 0; } /* 點分 思考怎麼修改,爬點分樹,亂改 似乎我只需要在點分樹裡面改貢獻就好了 */