原题链接:B2. LuoTianyi and the Floating Islands (Hard Version)
题目大意:
给定一棵 n n n 个点, n − 1 n - 1 n−1 条边的树,边权为 1 1 1 。现在假设有 k k k 个人随机地分布在这些点上,并且他们要走到同一个点 x x x 。
定义点 x x x 是好点,当且仅当这 k k k 个人走到 x x x 的距离之和最小。
现在问你好点 x x x 个数的期望值是多少,并输出期望值在 m o d 1 0 9 + 7 \mod 10^9+7 mod109+7 下的值。
解题思路:
首先注意到,当 k k k 为奇数时,无论我们怎么选,答案一定都会是 1 1 1 。
假设 k = 3 k=3 k=3 时,我们有如下这两种情况:
(橙色点为已选的 k k k 个点,三角标记的点为好点 x x x )
注意到上面这两种情况的好点 x x x 的个数均为 1 1 1 。
首先,由于点的个数不能被平均分配,对于我们所选的点 x x x 来说,我们的点 x x x 经过某一条边转移到另一个点 x ′ x' x′ 上时,对距离之和 ∑ d i s t \sum dist ∑dist 的贡献一定会不同。
假设我们在上图的三点在一条链上进行讨论,假设我们不选择三角符号标记的点 x = 3 x=3 x=3 作为好点,而是选择 x ′ = 2 x'=2 x′=2 的点作为好点。
对于点 x ′ = 2 x'=2 x′=2 这个位置而言,我们此时如果往左边走,左边的点的个数为 1 1 1 个(点 1 1 1),而右边的点的个数为 2 2 2 个(点 3 , 5 3,5 3,5)两边点的个数是不均等的。
往左走时,对
d
i
s
t
dist
dist 的增量为
Δ
l
=
−
1
+
2
=
1
\Delta_l=-1+2=1
Δl=−1+2=1 。
往右走时,对
d
i
s
t
dist
dist 的增量为
Δ
r
=
+
1
−
2
=
−
1
\Delta_r=+1-2=-1
Δr=+1−2=−1 。
显然 x ′ x' x′ 的距离之和还能继续被降低, x ′ x' x′ 不会是好点。下图不在链上的情况同理。
我们可以推出一个结论,当我们沿着边走的增量均相同时,我们的点 x x x 才会是好点,增量不同时,我们最后一定会走到其中一个点上,且该点四周的增量都相同,由于图是一棵树,可以证明这个点一定存在的,而 k k k 为奇数时,这个点唯一。
- 再考虑 k k k 为偶数的情况。
注意到,只有点沿着一条边走的增量相同时才会是好点,那么显然我们对于一条边来说,如果两边的联通分量放的点的个数相同,增量 Δ \Delta Δ 一定为 0 0 0。
(定义好边为所有点经过这一条边的增量为
0
0
0 的边,如上图为
(
2
,
3
)
,
(
3
,
4
)
(2,3),(3,4)
(2,3),(3,4) 这两条边 )
显然 k k k 为偶数时,我们可以向一条边的两端的连通分量分别放上 k 2 k \over 2 2k 个点,使得好点的个数变多。
而且还注意到由于存在增量这个东西,我们可以得出一个结论:好点 一定是连续分布的,并且 好边 的两端一定会是 好点 。
根据这个结论,我们可以得到:好边也一定连续成链,那么 好点的个数 = = = 好边的个数 + 1 +1 +1。
那么我们可以转化为图中一共有多少条好边。
我们考虑一条边 ( u , v ) (u,v) (u,v) 相连的两个连通分量,我们往 u u u 所在的连通分量放上 k 2 k \over 2 2k 个点,往 v v v 所在的连通分量放上 k 2 k \over 2 2k 个点,那么显然这一条边 ( u , v ) (u,v) (u,v) 就能成为好边。
我们假设边上两点 u , v u,v u,v 所在连通分量大小为 s i z [ u ] siz[u] siz[u]、 s i z [ v ] siz[v] siz[v] 。
那么这种方案数有 C s i z [ u ] k 2 ⋅ C s i z [ v ] k 2 C_{siz[u]}^{k \over 2} \cdot C_{siz[v]}^{k \over 2} Csiz[u]2k⋅Csiz[v]2k 种。
那么好边的总期望数就是 ∑ ( C s i z [ u ] k 2 ⋅ C s i z [ v ] k 2 ) C n k \sum( C_{siz[u]}^{k \over 2} \cdot C_{siz[v]}^{k \over 2}) \over C_{n}^{k} Cnk∑(Csiz[u]2k⋅Csiz[v]2k) 条。
那么化简后,好点的个数就是 ∑ ( C s i z [ u ] k 2 ⋅ C s i z [ v ] k 2 ) + C n k C n k \sum( C_{siz[u]}^{k \over 2} \cdot C_{siz[v]}^{k \over 2})+C_{n}^{k} \over C_{n}^{k} Cnk∑(Csiz[u]2k⋅Csiz[v]2k)+Cnk 个了。
考虑我们怎么进行枚举边 ( u , v ) (u,v) (u,v) 的操作,我们任取一个点作为根节点,然后执行一次 D F S DFS DFS 操作。
假设我们现在在点 u u u ,我们点 u u u 的子树大小为 s i z [ u ] siz[u] siz[u] ,我们的父节点为 f a u fa_u fau ,那么对于 ( f a u , u ) (fa_u,u) (fau,u) 这条边而言,其相连的两个连通分量的大小分别为 n − s i z [ u ] n-siz[u] n−siz[u] 、 s i z [ u ] siz[u] siz[u] 。
那么我们对答案的贡献即 C n − s i z [ u ] k 2 ⋅ C s i z [ v ] k 2 C_{n-siz[u]}^{k \over 2} \cdot C_{siz[v]}^{k \over 2} Cn−siz[u]2k⋅Csiz[v]2k 。
我们在统计所有答案后再乘上 C n k C_{n}^{k} Cnk 的逆元即可。
时间复杂度: O ( n ) O(n) O(n)
AC代码:
#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;
using ui64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;
//组合数板子
const int mod = 1e9 + 7;
struct Comb {
const int N;
vector<i64> fac, invfac;
Comb(int n) : N(n), fac(n + 2), invfac(n + 2) { init(); };
i64 qpow(i64 x, i64 p) {
i64 res = 1 % mod; x %= mod;
for (; p; p >>= 1, x = x * x % mod)
if (p & 1) res = res * x % mod;
return res;
}
i64 inv(i64 x) { return qpow(x, mod - 2); };
void init() {
fac[0] = 1;
for (int i = 1; i <= N; ++i) fac[i] = fac[i - 1] * i % mod;
invfac[N] = inv(fac[N]);
for (int i = N - 1; i >= 0; --i) invfac[i] = (invfac[i + 1] * (i + 1)) % mod;
}
i64 C(int n, int m) {
if (n < m || m < 0) return 0;
return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}
i64 A(int n, int m) {
if (n < m || m < 0) return 0;
return fac[n] * invfac[n - m] % mod;
}
};
Comb f(3e5);
const int N = 2e5 + 10;
vector<int> g[N];
i64 ans, inv;
int siz[N], n, k;
//DFS的同时计算贡献 C(n - siz[u], k / 2) * C(siz[u], k / 2)
void DFS(int u, int ufa) {
siz[u] = 1;
for (auto& v : g[u]) {
if (v == ufa) continue;
DFS(v, u);
siz[u] += siz[v];
ans = (ans + f.C(n - siz[v], k / 2) * f.C(siz[v], k / 2) % mod) % mod;
}
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= n - 1; ++i) {
int u, v; cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
if (k % 2 == 0) {
DFS(1, 0);
//inv是逆元
inv = f.inv(f.C(n, k));
ans = ans * inv % mod;
}
cout << (ans + 1) % mod;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1; //cin >> t;
while (t--) solve();
return 0;
}