题意

在一棵树上,边有边权,给定 $k$ 个特殊点,对于每个点 $i$ 求,从 $i$ 出发必须经过所有特殊点的最短路径长度(可重复)。

$n,k\leq10^5$

题解

不能直接对 dfn 排序然后线性做因为不对。

考虑树形 DP,答案就是从 $i$ 出发到每个有点的儿子一来一回,最终减去最长链,也可以跑到父亲那里,换根即可。对于最长链,有时需要次长链更新。

const int N = 5e5 + 5;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

namespace Main {
	int n, K;
	int head[N], tot;
	struct Edge {
		ll to, w, nxt;
	} e[N << 1];
	void add_edge(int u, int v, ll w) { e[++tot] = (Edge) {v, w, head[u]}, head[u] = tot; }
	bool key[N];
	ll siz[N];
	ll f[N], g[N], dis[N][2], id[N];
	void dfs1 (int u, int fa) {
		if (key[u]) siz[u] = 1;
		for (int i = head[u]; i; i = e[i].nxt) {
			int v = e[i].to; if (v == fa) continue;
			dfs1(v, u);
			if (siz[v]) {
				g[u] += g[v] + e[i].w * 2;
				if (dis[v][0] + e[i].w >= dis[u][0]) dis[u][1] = dis[u][0], dis[u][0] = dis[v][0] + e[i].w, id[u] = v;
				else if (dis[v][0] + e[i].w > dis[u][1]) dis[u][1] = dis[v][0] + e[i].w;
			}
			siz[u] += siz[v];
		}
	}
	void dfs2 (int u, int fa) {
		for (int i = head[u]; i; i = e[i].nxt) {
			int v = e[i].to; if (v == fa) continue;
			if (!siz[v]) f[v] = f[u] + 2 * e[i].w, dis[v][0] = dis[u][0] + e[i].w;
			else if (!(K - siz[v])) f[v] = g[v];
			else {
				f[v] = f[u];
				if (id[u] != v && dis[v][0] < dis[u][0] + e[i].w) dis[v][1] = dis[v][0], dis[v][0] = dis[u][0] + e[i].w, id[v] = u;
				else if (dis[v][0] < dis[u][1] + e[i].w) dis[v][1] = dis[v][0], dis[v][0] = dis[u][1] + e[i].w, id[v] = 1;
				else if (id[u] != v && dis[v][1] < dis[u][0] + e[i].w) dis[v][1] = dis[u][0] + e[i].w;
				else if (dis[v][1] < dis[u][1] + e[i].w) dis[v][1] = dis[u][1] + e[i].w;
			}
			dfs2(v, u);
		}
	}
	int main () {
		n = Read(), K = Read();
		for (int i = 1; i < n; i++) {
			int u = Read(), v = Read(), w = Read();
			add_edge(u, v, w), add_edge(v, u, w);
		}
		for (int i = 1; i <= K; i++) key[Read()] = 1;
		dfs1(1, 0); f[1] = g[1]; dfs2(1, 0);
		for (int i = 1; i <= n; i++) printf ("%lld\n", f[i] - dis[i][0]);
		return 0;
	}
}

int main () {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	Main::main();
	return 0;
}
EOF

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

博客信息

作者
Jayun
时间
2023-11-15 20:21:26
博客类型