【luogu P6419】Kamp(换根DP)

Kamp

题目链接:luogu P6419

题目大意

一棵树上有一些点有人,边有通过的长度,然后对于每个点,你从这个点出发经过所有人(不用回到原来位置)的最短时间。
其它人不会动,只有你去找人。

思路

首先只考虑一个点,发现如果回到原来位置是比较好搞的,就每次走完子树的里面要的就上来,如果子树里面没有要走的就不走。
(大概是 \(f_x=\sum\limits f_y+2e_{x,y}\),因为要走过去走回来,注意 \(y\) 要保证子树里面有人)

至于多个点换根 DP 即可。

然后考虑不走回来,那你少了的长度就是从最后一个人走回自己的距离。
那贪心的选离你最远的,思考一下发现是可以的因为可以最晚走到的条件是子树内除了自己没有人,安排一下子树的遍历顺序即可。

那这个对于每个点求距离它最远的人其实不难,就分成子树内和字数外,子树内好统计,子树外就维护子树内次小(不能跟最小在同一个儿子里面),然后要么从上面的 \(up\) 下来,要么在你这里开始往下(因为不能自己上自己下所以要维护次小)

然后就可以啦!

代码

#include<cstdio>
#include<iostream>
#define ll long long

using namespace std;

const int N = 5e5 + 100;
int n, k, a[N], le[N], KK;
int dis[N][2], sz[N], up[N];
ll ans[N], f[N];
bool p[N];
struct node {
	int x, to, nxt;
}e[N << 1];

void add(int x, int y, int z) {e[++KK] = (node){z, y, le[x]}; le[x] = KK;}

ll DP(int now, int father) {
	ll re = 0; if (p[now]) sz[now] = 1;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			ll x = DP(e[i].to, now); sz[now] += sz[e[i].to];
			if (!x && !p[e[i].to]) continue;
			x += 2ll * e[i].x; re += x;
			if (!x) continue;
			//不能是同一个子树所以不用再比次大
			if (dis[now][0] < dis[e[i].to][0] + e[i].x) dis[now][1] = dis[now][0], dis[now][0] = dis[e[i].to][0] + e[i].x;
				else if (dis[now][1] < dis[e[i].to][0] + e[i].x) dis[now][1] = dis[e[i].to][0] + e[i].x;
		}
	return f[now] = re;
}

void work(int now, int father) {
	ans[now] = f[now];
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			if (sz[1] - sz[e[i].to]) {
				up[e[i].to] = up[now] + e[i].x;
				if (dis[now][0] == dis[e[i].to][0] + e[i].x) up[e[i].to] = max(up[e[i].to], dis[now][1] + e[i].x);
					else up[e[i].to] = max(up[e[i].to], dis[now][0] + e[i].x);
			}
			
			bool re = 0, ree = 0;
			if (f[e[i].to] || p[e[i].to]) {
				f[now] -= 2ll * e[i].x + f[e[i].to]; re = 1;
			}
			if (f[now] || p[now]) {
				f[e[i].to] += 2ll * e[i].x + f[now]; ree = 1;
			}
			work(e[i].to, now);
			if (ree) {
				f[e[i].to] -= 2ll * e[i].x + f[now];
			}
			if (re) {
				f[now] += 2ll * e[i].x + f[e[i].to];
			}
		}
}

int main() {
	scanf("%d %d", &n, &k);
	for (int i = 1; i < n; i++) {
		int x, y, z; scanf("%d %d %d", &x, &y, &z);
		add(x, y, z); add(y, x, z);
	}
	for (int i = 1; i <= k; i++) scanf("%d", &a[i]), p[a[i]] = 1;
	
	ans[1] = DP(1, 0);
	work(1, 0);
	for (int i = 1; i <= n; i++) printf("%lld\n", ans[i] - max(up[i], dis[i][0]));
	
	return 0;
}
posted @ 2022-09-27 15:39  あおいSakura  阅读(28)  评论(0编辑  收藏  举报