洛谷P3345 [ZJOI2015]幻想乡战略游戏

传送门

题解:

先考虑一个简单的问题:怎么在有修改的情况下快速求一个点作为补给站的答案。

注意到和路径有关,所以很容易想到点分树。

接着可以想到一个十分暴力的做法,就是修改后,每次枚举相邻的点,如果更优就走过去。

很容易想到因为每次重心不会移动太远,这个是可以过的,数据也不太好造来卡这个做法。

那么有没有稳定的做法?

还是每次枚举子树,假设现在在点x,枚举的子树是y,发现y的答案比x优,那么就走到y子树的(不带权)重心去,这样相当于在点分树上逼近。

这个做法巧妙之处在于利用点分树的优美结构。

复杂度是 O ( n   l o g 2   n ∗ 20 ) O(n~log^2~n*20) O(n log2 n20)

Code:

#include<cstdio>
#define max(a, b) ((a) > (b) ? (a) : (b))
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 1e5 + 5;

int n, x, y, z, Q;
int fi[N], nt[N * 2], to[N * 2], v[N * 2], tot;

void link(int x, int y, int z) {
	nt[++ tot] = fi[x], to[tot] = y, v[tot] = z, fi[x] = tot;
}

int siz[N], mx[N], G, bz[N];

void fg(int x) {
	bz[x] = 1; siz[x] = 1; mx[x] = 0;
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]])
		fg(to[i]), siz[x] += siz[to[i]], mx[x] = max(mx[x], siz[to[i]]);
	mx[x] = max(mx[x], siz[0] - siz[x]);
	G = mx[G] < mx[x] ? G : x;
	bz[x] = 0;
}

int fa[N], dep[N], dis[19][N], D, bl[19][N];

void dfs(int x) {
	bz[x] = 1;
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]])
		dis[D][to[i]] = dis[D][x] + v[i], dfs(to[i]);
	bz[x] = 0;
}
void dg(int x) {
	fg(x); D = dep[x]; dfs(x); bz[x] = 1;
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]]) {
		siz[0] = siz[to[i]], G = 0, fg(to[i]);
		fa[G] = x, dep[G] = dep[x] + 1, bl[dep[G]][to[i]] = G;
		dg(G);
	}
}

struct nod {
	ll x, y;
} a[N], b[N];

void add(int x, ll c) {
	a[x].y += c;
	for(int p = x; p; p = fa[p]) {
		int k = dep[p] - 1;
		if(fa[p]) {
			a[fa[p]].x += c * dis[k][x], a[fa[p]].y += c;
			b[p].x += c * dis[k][x], b[p].y += c;
		}
	}
}

ll fd(int x) {
	ll s = 0;
	for(int p = x; p; p = fa[p]) {
		int k = dep[p];
		s += a[p].x + a[p].y * dis[k][x];
		if(fa[p]) s -= b[p].x + b[p].y * dis[k - 1][x];
	}
	return s;
}

int g;

int main() {
	scanf("%d %d", &n, &Q);
	fo(i, 1, n - 1) {
		scanf("%d %d %d", &x, &y, &z);
		link(x, y, z); link(y, x, z);
	}
	siz[0] = mx[0] = n; fg(1); int g0 = G; dg(G);
	fo(ii, 1, Q) {
		scanf("%d %d", &x, &y);
		add(x, y); g = g0;
		while(1) {
			ll s = fd(g); int c = 1;
			for(int i = fi[g]; i; i = nt[i]) {
				if(dep[to[i]] < dep[g]) continue;
				int y = bl[dep[g] + 1][to[i]];
				if(fd(to[i]) < s) {g = y; c = 0; continue;}
			}
			if(c) break;
		}
		printf("%lld\n", fd(g));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值