P6643 [CCO 2020] Mountains and Valleys

首先答案上界是 \(2n-2-D\),其中 \(D\) 是直径长度。显然非树边至多走两条。

但是实际上非树边只会走一条,因为对于一条边 \((u,v,w)\),如果 \(w\ge d(u,v)\) 就不优,所以此时有 \(D>w\),即 \(D\ge\lceil\frac{n}{3}\rceil+1\)。那么走两条边的代价至少 \(n-3+2\lceil\frac{n}{3}\rceil\ge 2n-2-\lceil\frac{n}{3}\rceil-1\),于是也是不优的。

那么考虑枚举这条边 \((u,v,w)\),那么对于原树的情况,我们会选一条路径 \(s\to t\) 只走一次,剩下的边走两次。加入 \((u,v,w)\) 后,考虑 \(u,v\) 在路径上投影 \(p,q\),如果 \(p=q\) 那么 \(u\to v\) 路径上都可以省一次。否则 \(u\to p,v\to q\) 可以省一次,但是 \(p\to q\) 要多走一次,注意我们可以少走一条边,即新的代价为 \(2n-2-d(s,t)-d(u,v)+2d(p,q)+w-2[p\neq q]\)

这个东西可以考虑把 \(u\to v\) 路径上边权翻成 \(-1\),那么 \(s\to t\) 就是要找一条直径。注意要分别统计含 \(-1\) 和不含的情况。可以换根做到总复杂度 \(O(nm)\)

考虑进一步优化。\(p=q\) 是简单的,此时相当于断掉 \(u\to v\) 路径所有边后,找剩余连通块直径的 \(\max\)。这个可以倍增预处理。

对于 \(p\neq q\),可以发现 \(d(s,t)+d(u,v)-2d(p,q)=d(s,u)+d(t,v)\)。我们要最小化 \(2n-4-d(s,u)-d(t,v)+w\),这个需要 \(<2n-2-D\),即有 \(D+w\le d(s,u)+d(t,v)+1\)

因为 \(w\ge \lceil\frac{n}{3}\rceil\),而 \(d(s,u),d(t,v)\le D\),所以有 \(d(s,u),d(t,v)\ge w-1\ge \lceil\frac{n}{3}\rceil-1\)。同时因为 \(d(u,v)>w\),也有 \(d(u,v)\ge \lceil\frac{n}{3}\rceil+1\)

我们取一条边 \((x,y)\),并分析其和 \(s,t,u,v\) 的关系。我们希望它在 \(p\to q\) 路径上。因为 \(d(s,u),d(t,v)\ge\lceil\frac{n}{3}\rceil-1\),因此两边直径点数应该 \(\ge\lceil\frac{n}{3}\rceil\),而且这个下界不能同时取到,因为 \(D+w\le d(s,u)+d(t,v)+1\),所以两边的直径点数之和 \(\ge D+w+1\ge 2\lceil\frac{n}{3}\rceil+2\)

因此首先不可能四个点都在一侧,否则这边子树大小 \(\ge D+w+1\),而另一边又有 \(\lceil\frac{n}{3}\rceil\) 矛盾。所以两侧都至少有一个点。并且对于一侧,不可能恰好有 \(s,t\) 或恰好有 \(u,v\),否则就不交了。

因此如果不满足 \((x,y)\)\(p\to q\) 上,那就只能一侧恰好有一个点,不妨设 \(x\) 方向有 \(s\)。那么 \(d(s,u)=d(s,x)+1+d(y,u)\),即需要 \(D+w-d(s,x)\le d(y,u)+d(t,v)+2\)

进一步放缩 \(D-d(s,x)\),考虑把 \(D\) 放缩成以 \(s\) 为起点,过 \((x,y)\) 的最长路,那么 \(D-d(s,x)\) 就是这条路径在 \(y\) 侧的部分再 \(+1\),而我们知道 \(v,t\) 都在 \(y\) 侧,且 \(d(v,t)\ge \lceil\frac{n}{3}\rceil-1\),所以 \(\max(d(v,y),d(t,y))\ge \lceil\frac{d(v,t)}{2}\rceil\ge \lceil\frac{n-3}{6}\rceil\)

因此 \(D-d(s,x)\ge \max(d(v,y),d(t,y))+1\ge \lceil\frac{n}{6}\rceil\),带回就有 \(w+\lceil\frac{n}{6}\rceil\le d(y,u)+d(t,v)+2\)。而 \(w\ge \lceil\frac{n}{3}\rceil\),因此 \(d(y,u)+d(t,v)+2\ge \lceil\frac{n}{2}\rceil\)

这说明,不妨设 \(x\) 一侧只有一个点,那么另一侧子树大小 \(\ge \lceil\frac{n}{2}\rceil\)。考虑换重心 \(root\) 为根,取出子树最大的两个子节点 \(a,b\),然后我们只对 \(a\to root\)\(b\to root\) 两条边,把它当作 \((x,y)\) 统计答案。

其实这就足够了,因为如果 \(a\to root\) 没统计上,那么就有一个点在 \(a\) 子树内,同时 \(b\to root\) 没统计上就有一个点在 \(b\) 子树。那么此时交的路径只能从 \(root\) 往剩下的点延申,而剩下的点子树大小 \(\le \lceil\frac{n}{3}\rceil-1\),和之前的要求矛盾。

枚举边之后只要两边分别找直径算,总复杂度加上前面的倍增是 1log。

#include <bits/stdc++.h>
using namespace std;

const int inf = 1e9;

template <typename T>
bool Chkmax(T &x, T y) { return (x < y) ? (x = y, 1) : 0; }
template <typename T>
bool Chkmin(T &x, T y) { return (x > y) ? (x = y, 1) : 0; }

const int kN = 5e5 + 5, kM = 2e6 + 5, kS = kN * 4, kLog = 22;
int n, m, e, tot;
struct Edge {
  int u, v, w;
} edge[kM];

vector<int> g[kN];
int fa[kN];
int dfn[kN], rev[kN], dep[kN], siz[kN];

struct ST {
  int mn[kLog][kN];
  int Min(int x, int y) { return dfn[x] < dfn[y] ? x : y; }
  void Build() {
    for(int i = 1; i < kLog; i++) {
      for(int j = 1; j + (1 << i) - 1 <= n; j++) {
        mn[i][j] = Min(mn[i - 1][j], mn[i - 1][j + (1 << i - 1)]);
      }
    }
  }
  int LCA(int x, int y) {
    if(x == y) return x;
    x = dfn[x];
    y = dfn[y];
    if(x > y) swap(x, y);
    int p = __lg(y - x++);
    return Min(mn[p][x], mn[p][y - (1 << p) + 1]);
  }
  int Dist(int x, int y) {
    return dep[x] + dep[y] - 2 * dep[LCA(x, y)];
  }
} st;

struct Diam {
  int x, y, d;
  Diam() { x = y = 0, d = -1; }
  Diam(int _x, int _y, int _d) { x = _x, y = _y, d = _d; }
} ;
Diam operator + (Diam x, Diam y) {
  if(x.d < 0) return y;
  if(y.d < 0) return x;
  int s = 0, t = 0, d = -1;
  if(Chkmax(d, x.d)) s = x.x, t = x.y;
  if(Chkmax(d, y.d)) s = y.x, t = y.y;
  for(int i : {x.x, x.y}) for(int j : {y.x, y.y}) {
    if(Chkmax(d, st.Dist(i, j))) s = i, t = j;
  }
  return Diam(s, t, d);
}

struct SGT {
  int T;
  Diam diam[kS];
  void Build() {
    for(T = 1; T <= n + 2; T <<= 1) ;
    for(int i = 1; i <= n; i++) diam[i + T] = Diam(rev[i], rev[i], 0);
    for(int i = T - 1; i; i--) diam[i] = diam[i << 1] + diam[i << 1 | 1];
  }
  Diam Query(int l, int r) {
    Diam ans;
    for(l += T, r += T + 1; l < r; l >>= 1, r >>= 1) {
      if(l & 1) ans = ans + diam[l++];
      if(r & 1) ans = ans + diam[--r];
    }
    return ans;
  }
} sgt;

Diam pre[kN], suf[kN];
int w[kN], s[kN], sub[kN];
int jp[kN];

void DFS(int x, int fa) {
  ::fa[x] = fa;
  dfn[x] = ++tot;
  rev[tot] = x;
  dep[x] = dep[fa] + 1;
  siz[x] = 1;
  st.mn[0][tot] = fa;
  for(int to : g[x]) {
    if(to != fa) {
      DFS(to, x);
      siz[x] += siz[to];
    }
  }
}

bool IsAnc(int x, int y) {
  return (dfn[x] <= dfn[y]) && (dfn[y] < dfn[x] + siz[x]);
}

void DFS2(int x, int fa) {
  if(fa) w[x] = (sgt.Query(dfn[fa], dfn[x] - 1) + sgt.Query(dfn[x] + siz[x], dfn[fa] + siz[fa] - 1)).d;
  if(jp[jp[fa]] && (dep[jp[jp[fa]]] + dep[fa] == 2 * dep[jp[fa]])) {
    jp[x] = jp[jp[fa]];
    s[x] = max({w[x], s[fa], s[jp[fa]]});
  } else {
    jp[x] = fa;
    s[x] = w[x];
  }

  sub[x] = sgt.Query(dfn[x], dfn[x] + siz[x] - 1).d;
  for(int to : g[x]) {
    if(to != fa) DFS2(to, x);
  }
}

int Root(int x, int fa) {
  int mx = n - siz[x];
  for(int to : g[x]) {
    if(to == fa) continue;
    if(int tmp = Root(to, x)) return tmp;
    Chkmax(mx, siz[to]);
  }
  return (mx * 2 <= n) * x;
}

int Query(int s, int t) {
  if(s == t) return sgt.diam[1].d;
  if(dep[s] < dep[t]) swap(s, t);
  int ans = 0;
  if(IsAnc(t, s)) {
    Chkmax(ans, sub[s]);
    int p = fa[s], v = dep[t] + 1;
    while(dep[p] > v) {
      if(dep[jp[p]] >= v) Chkmax(ans, ::s[p]), p = jp[p];
      else Chkmax(ans, ::w[p]), p = fa[p];
    }
    Chkmax(ans, (pre[dfn[p] - 1] + suf[dfn[p] + siz[p]]).d);
    return ans;
  }

  Chkmax(ans, sub[s]);
  Chkmax(ans, sub[t]);
  while(dep[s] > dep[t]) {
    if(dep[jp[s]] >= dep[t]) Chkmax(ans, ::s[s]), s = jp[s];
    else Chkmax(ans, ::w[s]), s = fa[s];
  }
  while(fa[s] != fa[t]) {
    if(jp[s] != jp[t]) {
      Chkmax(ans, max(::s[s], ::s[t]));
      s = jp[s], t = jp[t];
    } else {
      Chkmax(ans, max(::w[s], ::w[t]));
      s = fa[s], t = fa[t];
    }
  }
  
  int lca = fa[s];
  if(dfn[s] > dfn[t]) swap(s, t);
  Diam tmp = pre[dfn[s] - 1] + suf[dfn[t] + siz[t]];
  tmp = tmp + sgt.Query(dfn[s] + siz[s], dfn[t] - 1);
  Chkmax(ans, tmp.d);
  return ans;
}

int nsz[kN];

void DFS3(int x, int fa) {
  nsz[x] = 1;
  for(int to : g[x]) {
    if(to != fa) {
      DFS3(to, x);
      nsz[x] += nsz[to];
    }
  }
}

int Solve(int x, int y) {
  if(fa[y] == x) swap(x, y);
  Diam in = sgt.Query(dfn[x], dfn[x] + siz[x] - 1);
  Diam out = pre[dfn[x] - 1] + suf[dfn[x] + siz[x]];

  int ans = inf;
  for(int i = 1; i <= e; i++) {
    int u = edge[i].u;
    int v = edge[i].v;
    int w = edge[i].w;
    if(w > n) continue;
    bool fu = IsAnc(x, u), fv = IsAnc(x, v);
    if(fu == fv) continue;
    if(!fu && fv) swap(u, v), swap(fu, fv);
    int sum = max(st.Dist(u, in.x), st.Dist(u, in.y)) + max(st.Dist(v, out.x), st.Dist(v, out.y));
    Chkmin(ans, 2 * n - 4 - sum + w);
  }
  return ans;
}

int main() {
  // freopen("tsp.in", "r", stdin);
  // freopen("tsp.out", "w", stdout);
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  for(int i = 1; i <= m; i++) {
    int u, v, w;
    cin >> u >> v >> w, u++, v++;
    if(w == 1) {
      g[u].push_back(v);
      g[v].push_back(u);
    } else {
      edge[++e] = Edge {u, v, w};
    }
  }

  DFS(1, 0);
  st.Build();
  sgt.Build();
  DFS2(1, 0);
  for(int i = 1; i <= n; i++) pre[i] = pre[i - 1] + Diam(rev[i], rev[i], 0);
  for(int i = n; i; i--) suf[i] = suf[i + 1] + Diam(rev[i], rev[i], 0);

  int ans = 2 * n - 2 - pre[n].d;
  for(int i = 1; i <= e; i++) {
    int u = edge[i].u;
    int v = edge[i].v;
    int w = edge[i].w;
    if(w >= min(st.Dist(u, v), (n + 1) / 2)) {
      edge[i].w = n + 1;
      continue;
    }
    Chkmin(ans, 2 * n - 2 - Query(u, v) - st.Dist(u, v) + w);
  }

  int cen = Root(1, 0);
  DFS3(cen, 0);
  sort(g[cen].begin(), g[cen].end(),
    [&](int i, int j) { return nsz[i] > nsz[j]; });
  if(g[cen].size() > 0) Chkmin(ans, Solve(g[cen][0], cen));
  if(g[cen].size() > 1) Chkmin(ans, Solve(g[cen][1], cen));
  cout << ans << "\n";
  return 0;
}
posted @ 2026-06-22 16:14  CJzdc  阅读(9)  评论(1)    收藏  举报