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\) 的子树时树的直径,则答案即为:

\[\max_{(u, v)\in \text{T}} F(u, v) + F(v, u) \]

那要如何处理 \(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;
}
posted @ 2024-04-11 19:51  Luckyblock  阅读(125)  评论(0编辑  收藏  举报