「NOIP2015」运输计划

知识点:二分答案,树上差分

原题面:Loj Luogu


题意简述

给定一棵 \(n\) 个节点的树,边有边权。
给定 \(m\) 条路径,定义花费为所有路径长度的最大值。
现可将任意一条边的权值置零,求最小的花费。
\(100\le n\le 3\times 10^5\)\(1\le m\le 3\times 10^5\)\(0\le\) 边权 \(\le 10^3\)


分析题意

最大值最小,考虑二分答案,枚举置零边权后路径长度的最大值 \(mid\)

Check 时仅需考虑长度 \(\ge mid\) 的路径即可,其他路径已经合法。
问题转化为能否找到一条边,同时在所有长度 \(\ge mid\) 的路径上,且将其置零后能使所有路径合法。
即其权值 \(\ge\) 最长的路径长度 \(-mid\)

对于 同时出现 这一限制,考虑树上差分维护各边在路径中 出现的次数。
对于 权值大小 这一限制,预处理出所有路径的长度。
Dfs 还原时判断是否存在一条边满足上述条件即可。

修改的路径是已知的,可先预处理所有的 lca,差分时直接调用。
单次 Check 复杂度上限为 \(O(m + n)\)\(n,m\) 同阶,总复杂度 \(O(n\log n)\) 级别。


爆零小技巧

轻微卡常,注意实现。


代码实现

//知识点:二分答案,树上差分 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 3e5 + 10;
const int kMaxm = (kMaxn << 1);
//=============================================================
struct Route {
  int a, b, lca, val;
} a[kMaxn];
int n, m, ans;
int e_num, head[kMaxn], v[kMaxn << 1], w[kMaxn << 1], ne[kMaxn << 1];
int dep[kMaxn], fa[kMaxn][25], sum[kMaxn][25];
int diff[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_, w[e_num] = w_;
  ne[e_num] = head[u_], head[u_] = e_num;
}
bool CompareRoute(Route fir, Route sec) {
  return fir.val > sec.val;
}
void Dfs1(int u_, int fa_) {
  dep[u_] = dep[fa_] + 1;
  fa[u_][0] = fa_;
  for (int i = 1; i <= 22; ++ i) {
    fa[u_][i] = fa[fa[u_][i - 1]][i - 1];
    sum[u_][i] = sum[u_][i - 1] + sum[fa[u_][i - 1]][i - 1];
  }
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i], w_ = w[i];
    if (v_ == fa_) continue ;
    sum[v_][0] = w_;
    Dfs1(v_, u_);
  }
}
void GetLca(int id_, int u_, int v_) {
  if (dep[v_] > dep[u_]) std :: swap(u_, v_);
  for (int i = 22; i >= 0; -- i) {
    if (dep[fa[u_][i]] >= dep[v_]) {
      a[id_].val += sum[u_][i];
      u_ = fa[u_][i];
    }
  }
  if (u_ == v_) {
    a[id_].lca = u_;
    return ;
  }
  for (int i = 22; i >= 0; -- i) {
    if (fa[u_][i] != fa[v_][i]) {
      a[id_].val += sum[u_][i] + sum[v_][i];
      u_ = fa[u_][i];
      v_ = fa[v_][i];
    }
  }
  a[id_].val += sum[u_][0] + sum[v_][0];
  a[id_].lca = fa[u_][0];
}
bool Dfs2(int u_, int fa_, int cnt_, int lim_) {
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue ;
    if (Dfs2(v_, u_, cnt_, lim_)) return true;
    diff[u_] += diff[v_];
  }
  return (diff[u_] == cnt_ && a[1].val - sum[u_][0] <= lim_);
}
bool Check(int lim_) {
  memset(diff, 0, sizeof (diff));
  int cnt = 0;
  for (int i = 1; i <= m; ++ i) {
    if (a[i].val <= lim_) break;
    ++ cnt;
    diff[a[i].a] ++;
    diff[a[i].b] ++;
    diff[a[i].lca] -= 2;
  }
  return Dfs2(1, 0, cnt, lim_);
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_), AddEdge(v_, u_, w_);
  }
  Dfs1(1, 0);
  for (int i = 1; i <= m; ++ i) {
    a[i].a = read(), a[i].b = read();
    GetLca(i, a[i].a, a[i].b);
  }
  std :: sort(a + 1, a + m + 1, CompareRoute);
  for (int l = 1, r = a[1].val; l <= r; ) {
    int mid = (l + r) >> 1;
    if (Check(mid)) {
      ans = mid;
      r = mid - 1;
    } else {
      l = mid + 1;
    }
  }
  printf("%d\n", ans);
  return 0;
}
posted @ 2020-09-26 08:12  Luckyblock  阅读(138)  评论(1编辑  收藏  举报