开车 社论

题面

有一棵 \(n\) 个点的树和 \(k\) 个关键点 .

对于 \(1\)\(n\) 中每个 \(i\) 求出 \(i\) 走过这 \(k\) 个关键点的最短路长度 .

\(k\le n\le 5\times10^5\) .

题解

整体思路

对于每个点 \(u\),我们这样走:

  • 首先走到最近的关键点 .
  • 然后把其他关键点全走一遍,这里显然是关键点间权值的二倍 .
  • 因为不一定要回去,所以可以去掉一条链的贡献,去掉最长链即可 .

分别拆解开求即可

关键点之间

首先要考虑的显然只有关键点间的东西 .

如果子树内有关键点,显然就满足条件 .

我们维护一个 \(K_u\) 表示其是否在关键点间,然后随便选一个关键点开始 DFS,在回溯的时候标记 \(K\) 即可 .

bool findK(int u, int f)
{
	fa[u] = f;
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if (v == f) continue;
		if (findK(v, u)){K[u] = true; all += w;}
	} return K[u];
}

这里处理了关键点间权值 \(all\) .

最近关键点

随便选一个关键点开始 DFS,记录下最近的关键点以及到它的距离,遇到关键点就重置即可 .

void distK(int u, int nea, ll s)
{
	dk[u] = s; near[u] = nea;
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if (v == fa[u]) continue;
		if (K[v]) distK(v, v, 0);
		else distK(v, nea, s+w);
	}
}

最远关键点

对于每个点维护关键点间最长链和次长链 .

于是每个点的最远关键点可以拆成最长链和边拼起来,如果正好在最长链上就是次长做贡献 .

还是看代码理解吧 .

ll gChain(int u)
{
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if ((v == fa[u]) || !K[v]) continue;
		ll now = gChain(v) + w;
		if (ch1[u] < now){ch2[u] = ch1[u]; chp2[u] = chp1[u]; ch1[u] = now; chp1[u] = v;}
		else if (ch2[u] < now){ch2[u] = now; chp2[u] = v;}
	} return ch1[u];
}
void farK(int u, ll s)
{
	far[u] = max(s, ch1[u]);
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if ((v == fa[u]) || !K[v]) continue;
		if (v == chp1[u]) farK(v, max(s, ch2[u]) + w);
		else farK(v, max(s, ch1[u]) + w);
	}
}

让我们把所有元素结合到一起

完整代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
#include <set>
#include <bitset>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
template<typename T>
inline T chkmin(T& a, const T& b){if (a > b) a = b; return a;}
template<typename T>
inline T chkmax(T& a, const T& b){if (a < b) a = b; return a;}
const int Sig = 33, N = 1e6+500, INF = 0x3f3f3f3f;
int n, k;
bool K[N];
ll all, fa[N], ch1[N], ch2[N], chp1[N], chp2[N], dk[N], near[N], far[N]; // 事实上 chp2 没有用 
vector<pair<int, int> > g[N];
inline void addedge(int u, int v, int w){g[u].push_back(make_pair(v, w));}
inline void ade(int u, int v, int w){addedge(u, v, w); addedge(v, u, w);}
bool findK(int u, int f) // k 点间
{
	fa[u] = f;
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if (v == f) continue;
		if (findK(v, u)){K[u] = true; all += w;}
	} return K[u];
}
ll gChain(int u) // 最长次长链 对应的儿子 
{
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if ((v == fa[u]) || !K[v]) continue;
		ll now = gChain(v) + w;
		if (ch1[u] < now){ch2[u] = ch1[u]; chp2[u] = chp1[u]; ch1[u] = now; chp1[u] = v;}
		else if (ch2[u] < now){ch2[u] = now; chp2[u] = v;}
	} return ch1[u];
}
void distK(int u, int nea, ll s) // 最近 k 点,到最近 k 点,
{
	dk[u] = s; near[u] = nea;
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if (v == fa[u]) continue;
		if (K[v]) distK(v, v, 0);
		else distK(v, nea, s+w);
	}
}
void farK(int u, ll s) // 到最远 k 点的距离 
{
	far[u] = max(s, ch1[u]);
	for (auto e : g[u])
	{
		int v = e.first, w = e.second;
		if ((v == fa[u]) || !K[v]) continue;
		if (v == chp1[u]) farK(v, max(s, ch2[u]) + w);
		else farK(v, max(s, ch1[u]) + w);
	}
}
int main()
{
	scanf("%d%d", &n, &k);
	for (int i=1, u, v, w; i<n; i++) scanf("%d%d%d", &u, &v, &w), ade(u, v, w);
	int ra;
	for (int i=1, x; i<=k; i++){scanf("%d", &x); K[x] = true; ra = x;}
	findK(ra, 0); gChain(ra); distK(ra, ra, 0); farK(ra, 0);
	for (int i=1; i<=n; i++) printf("%lld\n", 2*all + dk[i] - far[near[i]]);
	return 0;
} 

Bonus

两遍 DFS(换根 dp):https://www.cnblogs.com/SoyTony/p/15893793.html

虚树:似乎是经典题

posted @ 2022-02-14 18:03  Jijidawang  阅读(53)  评论(0编辑  收藏  举报
😅​