[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;
}
/*
点分
思考怎么修改,爬点分树,乱改 
似乎我只需要在点分树里面改贡献就好了 
*/
posted @ 2022-03-21 22:00  皮皮刘  阅读(33)  评论(0编辑  收藏  举报