CF2161F SubMST 题解

Description

给定一棵无向无权树 \(T\),它有 \(n\) 个顶点。

定义一个包含 \(n\) 个顶点的完全无向加权图 \(G\),其中 \(G\) 中顶点 \(u\)\(v\) 之间的边权等于它们在原树 \(T\) 中的最短距离。

对于所有可能的顶点子集,考虑由该子集诱导出的 \(G\) 的子图,并求出该子图的最小生成树的权值。

要求计算所有可能顶点子集的最小生成树权值之和。

由于这个数可能非常大,你只需要输出结果对 \(10^9 + 7\) 取模后的值。

\(n\leq 5000\)

Solution

首先最小生成树显然是无法直接做的,考虑用类似 kruskal 算法的方式,按照边权从小到大加边,计算每种边权的边总共加了多少条。

对于所有距离不超过 \(k\) 的点之间的连边可以看成钦定一个“中点”\(x\)(可以是边上的中点),然后将所有与 \(x\) 距离不超过 \(\frac{k}{2}\) 的点两两连边。

计算求最小生成树上边权为 \(k\) 的边加入的数量时,把这些距离为 \(k\) 的边放在中点上计算。


考虑一组选点方案的最小生成树中,挂在中点 \(x\) 处有长度为 \(k\) 的边的条件是什么。

经过手玩,可以发现条件是 \(x\) 所有子树中距离 \(x\) 的最短距离不小于 \(\frac{k}{2}\),且存在至少两个子树最短距离恰好是 \(\frac{k}{2}\)

证明

由于存在挂在 \(x\) 上的距离为 \(k\) 的边,所以肯定要有至少两个子树最短距离是 \(\frac{k}{2}\)。如果存在一个子树最短距离小于 \(\frac{k}{2}\),就说明那两个最短距离是 \(\frac{k}{2}\) 的子树可以与这个最近的子树连距离小于 \(k\) 的边,也就矛盾了。

挂在 \(x\) 的边数也就是最短距离是 \(\frac{k}{2}\) 的子树数量减一。

容易发现这么计算不会算少,但是会不会算多呢?即两个通过距离小于 \(k\) 构成的连通块之间会不会有很多距离等于 \(k\) 的边,且中点不唯一,此时就会算重。经过手玩,会发现这是不必要的关心。

证明

如果不唯一,则设这对连通块之间的一条长度为 \(k\) 的边是 \((a,b)\),中点是 \(x\);另一条边是 \((c,d)\),中点是 \(y\)\(x\neq y\))。

那么 \(a,c\)\(b,d\) 分别在 \(x\) 的同一个子树中。且 \(\text{dis}(x,a)=\text{dis}(x,b)=\frac{k}{2}\)\(\text{dis}(x,c)+\text{dis}(x,d)=k\)\(\text{dis}(x,c),\text{dis}(x,d)\geq\frac{k}{2}\)

由于 \(x\neq y\),所以 \(\text{dis}(x,c)\neq\text{dis}(x,d)\),这说明 \(\text{dis}(x,c)\)\(\text{dis}(x,d)\) 至少有一个数小于 \(\frac{k}{2}\),与 \(x\) 每个子树的最短距离都大于等于 \(\frac{k}{2}\) 矛盾。

所以根据上面的做法,枚举中点 \(x\) 和距离 \(k\) 后,对于 \(x\) 的每个儿子的子树,对它内部最短距离等于 \(\frac{k}{2}\) 的方案数 乘上其余子树最短距离都大于等于 \(\frac{k}{2}\) 的方案数求和。

最后减去所有子树的最短距离都大于等于 \(\frac{k}{2}\),且至少一个子树的最短距离是 \(\frac{k}{2}\) 的方案数即可。

时间复杂度:\(O(n^2)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e4 + 5, kMod = 1e9 + 7;

int n;
int pw[kMaxN], dep[kMaxN];
int dfn_cnt, dfn[kMaxN], idx[kMaxN], sz[kMaxN];
int cnt[kMaxN];
std::vector<int> G[kMaxN];

int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void prework(int n = 1e4) {
  pw[0] = 1;
  for (int i = 1; i <= n; ++i) pw[i] = 2ll * pw[i - 1] % kMod;
}

void dfs(int u, int fa) {
  idx[dfn[u] = ++dfn_cnt] = u, sz[u] = 1, dep[u] = dep[fa] + 1;
  for (auto v : G[u]) {
    if (v == fa) continue;
    dfs(v, u);
    sz[u] += sz[v];
  }
}

void solve(int u) {
  dfn_cnt = 0, dep[0] = -1, dfs(u, 0);
  static int cc[kMaxN];
  std::fill_n(cc + 1, 2 * n + 1, 0);
  for (auto v : G[u]) {
    for (int i = dfn[v]; i <= dfn[v] + sz[v] - 1; ++i) {
      int x = idx[i];
      if (x <= n) ++cc[dep[x]];
    }
  }
  for (int i = 2 * n; i; --i) {
    cc[i] += cc[i + 1];
    dec(cnt[i], sub(pw[cc[i]], pw[cc[i + 1]]));
  }
  for (auto v : G[u]) {
    static int ct[kMaxN];
    std::fill_n(ct + 1, 2 * n + 1, 0);
    for (int i = dfn[v]; i <= dfn[v] + sz[v] - 1; ++i) {
      int x = idx[i];
      if (x <= n) ++ct[dep[x]];
    }
    for (int i = 2 * n ; i; --i) {
      ct[i] += ct[i + 1];
      inc(cnt[i], 1ll * sub(pw[ct[i]], pw[ct[i + 1]]) * pw[cc[i] - ct[i]] % kMod);
    }
  }
  // for (int i = 1; i <= n; ++i) dec(cnt[i], sub(pw[cc[i]], pw[cc[i + 1]]));
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 0; i <= 2 * n - 1; ++i) G[i].clear(), cnt[i] = 0;
  for (int i = 1; i < n; ++i) {
    int u, v;
    std::cin >> u >> v;
    G[u].emplace_back(i + n), G[i + n].emplace_back(u);
    G[v].emplace_back(i + n), G[i + n].emplace_back(v);
  }
  for (int i = 1; i <= 2 * n - 1; ++i) solve(i);
  int ans = 0;
  for (int i = 1; i <= n; ++i) inc(ans, 1ll * i * cnt[i] % kMod);
  std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  std::cin >> T;
  prework();
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-11-13 17:28  下蛋爷  阅读(32)  评论(0)    收藏  举报