1. 程式人生 > 其它 >[ZJOI2015] 地震後的幻想鄉

[ZJOI2015] 地震後的幻想鄉

前言

點分樹練習題。

題目

洛谷

LOJ

講解

我們發現這道題相當於找樹上帶權重心。

有一個我認為挺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;
}
/*
點分
思考怎麼修改,爬點分樹,亂改 
似乎我只需要在點分樹裡面改貢獻就好了 
*/