2022 CCPC Guilin Site G
Link:https://codeforces.com/gym/104008/problem/G
知识点:换根 DP
好久没写单题题解了,这次遇到很有启发性的东西来写下。
第一次见这种换根 DP 的写法,牛逼。
参考:2022 China Collegiate Programming Contest (CCPC) Guilin Site(持续更新) - 空気力学の詩 - 博客园。
简述
给定一棵 \(n\) 个节点的无根树,点有点权 \(a_i\)。
要求从其中选择两条简单路径,记可获得的价值为树上仅被其中某一条路径覆盖的节点的权值之和。
求可获得的最大价值。
\(1\le n\le 2\times 10^5\),\(1\le a_i\le 10^4\)。
3S,512MB。
分析
大力手玩下,发现最优的情况下,两条路径至多有一个交点。则最终有贡献的节点构成的形态一定为下列两种情况之一:
- 以某个节点 \(u\) 为一端的四条链(不包含节点 \(u\),且长度可为 0)。
- 两条不相交的路径。
若不为上述两种形态,则可以通过添加某些节点调整成上述两种形态,且获得更多的贡献。
第一种情况非常简单,换根 DP 维护以每个节点 \(u\) 为端点的所有链 \(\operatorname{chain}(u)\),并按长度排序,枚举所有节点取其中长度前 4 大的即可。
第二种情况实际上可看做断开树中的一条边,在得到的两根树分别求带权直径。这是一般的换根 DP 实现起来比较麻烦的,从大神博客里学到了一种神奇写法。考虑记 \(F(u, v)\) 表示在以 \(u\) 为根的树中,钦定删去邻接点 \(v\) 的子树时树的直径,则答案即为:
那要如何处理 \(F\) 呢?不妨对于 \(F(u, v)\),钦定 \(v\) 为 \(u\) 的父节点,考虑记忆化搜索。首先根据第一种情况预处理的 \(\operatorname{chain}(u)\),从中找出不以 \(v\) 为端点的最长和次长链更新经过该节点的路径的贡献,然后枚举其子节点考虑其子树中路径的贡献即可。
虽然 \(F\) 是一个二维的状态,但由于钦定了 \((u, v)\in \text{T}\) 显然其中仅有 \(O(n)\) 级别个状态是有贡献的。
时限比较大可以比较随意地实现,\(\operatorname{chain}(u)\) 与 \(F\) 均仅有 \(O(n)\) 个有贡献状态,考虑直接使用排序和 map
进行维护,则总时间复杂度为 \(O(n\log n)\) 级别。
代码
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define mp std::make_pair
const int kN = 2e5 + 10;
//=============================================================
int n, a[kN];
int edgenum, head[kN], v[kN << 1], ne[kN << 1];
int ans;
std::vector <std::pair <int, int> > maxl[kN];
std::map <int, int> f[kN];
//=============================================================
void Add(int u_, int v_) {
v[++ edgenum] = v_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
void Dfs1(int u_, int fa_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
Dfs1(v_, u_);
maxl[u_].push_back(mp(a[v_], v_));
if (!maxl[v_].empty()) maxl[u_].back().first += maxl[v_][0].first;
}
std::sort(maxl[u_].begin(), maxl[u_].end(), std::greater <std::pair <LL, int> >());
}
void Dfs2(int u_, int fa_, int dis_) {
if (fa_) {
maxl[u_].push_back(mp(dis_, fa_));
std::sort(maxl[u_].begin(), maxl[u_].end(), std::greater <std::pair <LL, int> >());
}
while (maxl[u_].size() < 4) maxl[u_].push_back(mp(0, 0));
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
if (v_ != maxl[u_][0].second) Dfs2(v_, u_, maxl[u_][0].first + a[u_]);
else Dfs2(v_, u_, maxl[u_][1].first + a[u_]);
}
}
int F(int u_, int fa_) {
if (f[u_].count(fa_)) return f[u_][fa_];
int p1 = 0;
while (maxl[u_][p1].second == fa_) ++ p1;
int p2 = p1 + 1;
while (maxl[u_][p2].second == fa_) ++ p2;
int ret = maxl[u_][p1].first + maxl[u_][p2].first + a[u_];
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
ret = std::max(ret, F(v_, u_));
}
f[u_][fa_] = ret;
return ret;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
for (int i = 1; i < n; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
Add(u_, v_), Add(v_, u_);
}
Dfs1(1, 0), Dfs2(1, 0, 0);
for (int i = 1; i <= n; ++ i) {
int sum = 0;
for (int j = 0; j < 4; ++ j) sum += maxl[i][j].first;
ans = std::max(ans, sum);
}
for (int u_ = 1; u_ <= n; ++ u_) {
for (int i = head[u_]; i; i = ne[i]) {
ans = std::max(ans, F(u_, v[i]) + F(v[i], u_));
}
}
std::cout << ans << "\n";
return 0;
}