Codeforces 1578L. Labyrinth

Codeforces 1578L. Labyrinth

第一眼是一个图论题, 感觉很难很难. 于是考虑发现性质.

引理 0 起点可以是除了 \(1\) 的任何点.

证明 设一开始身体的宽度为 \(w\) , 则对于任一点 \(u\) , 一定存在一条路径 \(1\rightarrow u\) 满足路径上的边权都 \(\ge w\) . 于是可以先不吃糖走到任一节点 \(u\) 再开始.

引理 1 选的边一定在原图的最大生成树上.

证明 使用 \(\text{Kruskal}\) 算法流程便可推出这个结论.

于是问题就转化成了一个树上问题, 看起来简单多了.

引理 2 对于当前树上最小的边, 删去这条边后剩下两个连通块, 一定是先吃完一个连通块的点, 再走过这条边, 并吃完另一个连通块的点.

证明 我们假设删除这条边剩下的连通块为 \(u, v\) . 假设我们先吃掉连通块 \(u\) 的一部分, 然后走过这条边, 再吃完连通块 \(v\) , 最后再走回来吃完 \(u\) .

如果这种方案可行, 那么一定也可以先吃完连通块 \(v\) , 然后走过这条边, 再吃完连通块 \(u\) . 并且由于吃东西只会使身体变宽, 这一种方案肯定比上一种更优.

因此, 我们可以把原树转化为一个 \(\text{Kruskal}\) 重构树. 连通块问题也就转化成了子树问题.

\(f_u\) 表示以 \(u\) 为根的子树中, 当前在 \(u\) 位置, 当前最多可以多胖, 使得可以吃完该子树. 令 \(s_u\) 表示以 \(u\) 为根的子树内所有点(虚点)的权值和.

假设现在有一条权值为 \(v\) 的边合并了 \(x,y\) 两颗子树, 并在 \(\text{Kruskal}\) 重构树上新建了节点 \(z\) , 那么如果先吃 \(x\) 子树, 则必须满足三个条件:

  • 可以吃完子树 \(x\) . 于是 \(f_z \le f_x\) ;

  • 吃完子树 \(x\) 后可以到 \(z\) 节点, 并经过 \(z\) . 于是 \(f_z+s_x\le v\) ;

  • 吃完子树 \(x\) 后可以继续吃掉子树 \(y\) . 于是 \(f_z+s_x\le f_y\) .

所以转移为 \(\min\{v,\,f_y,\,f_x+s_x\}-s_x\rightarrow f_z\) .

还有可能先吃掉 \(y\) 子树, 于是取 \(\max\) 即可.

总时间复杂度 \(\mathcal O(n\log n)\)

参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int64_t inf = 0x3f3f3f3f3f3f3f3f;
static constexpr int Maxn = 2e5 + 5, Maxm = 1e5 + 5;
int n, m, en, fa[Maxn];
int64_t a[Maxn], s[Maxn], dp[Maxn];
struct Edge {
  int u, v;
  int64_t w;
  Edge() = default;
  friend bool operator < (const Edge &lhs, const Edge &rhs) {
    return lhs.w > rhs.w;
  }
} e[Maxm];
int fnd(int x) { return fa[x] == x ? x : fa[x] = fnd(fa[x]); }
int main(void) {
  scanf("%d%d", &n, &m); en = n;
  for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
  for (int i = 1; i <= m; ++i) scanf("%d%d%lld", &e[i].u, &e[i].v, &e[i].w);
  sort(e + 1, e + m + 1);
  for (int i = 1; i <= n; ++i) fa[i] = i, s[i] = a[i];
  memset(dp, inf, sizeof(dp));
  for (int i = 1; i <= m; ++i) {
    int u = e[i].u, v = e[i].v;
    int64_t w = e[i].w;
    u = fnd(u), v = fnd(v);
    if (u == v) continue;
    ++en;
    fa[u] = fa[v] = fa[en] = en;
    s[en] = s[u] + s[v];
    dp[en] = max(min(w, dp[u]) - s[v], min(w, dp[v]) - s[u]);
  }
  if (dp[en] <= 0) dp[en] = -1;
  printf("%lld\n", dp[en]);
  exit(EXIT_SUCCESS);
} // main
posted @ 2021-11-07 10:15  cutx64  阅读(118)  评论(0)    收藏  举报