开车 社论
题面
有一棵 \(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
虚树:似乎是经典题
以下是博客签名,正文无关
本文来自博客园,作者:Jijidawang,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/15893637.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ