题意
给一棵树,每次可以染色一个叶子(只能是原树上的叶子,且每个叶子可以染色多次),求使不经过被染色的节点的直径减小的期望步数。
思路
可能会有点长,大概的理一理。
一、求直径
nothing to say。走流程。
二、分集合
然后开始观察这棵树。直径可能有多条。然后我们回顾一下直径的一些性质:
- 所有直径一定交于连续的一段
- 直径的交集一定包含每条直径的中心(边或点)
证明略。
那么如何可以减小一棵树的直径的长度呢?感性理解,肯定要把所有直径都至少削掉一个叶子。那么是不是可以把直径的端点分成一些集合,使得每个集合内部两点连线无法形成直径。所以现在问题变成了去掉一些元素使得只剩下一个集合。
那么如何分集合呢?我们分直径长度的奇偶性考虑。
长度奇数,则有一条处于正中间的边,被所有直径经过。把这条边剖开,两边的端点都无法形成直径,那么就此可以分成2个集合。
长度偶数,则有一个点处在中间。同理,去掉这个点,所有直径都断了,所以对于中间点的所有子树,每个子树包含的直径端点构成一个集合。
好了,问题已经解决一半了。
三、统计答案
总体思路:可以把叶子被染色的顺序看成一个排列,累加每个排列的期望值,再除以总排列数即总答案。
假设 m m m表示总叶子数, d d d表示是直径端点的叶子数。
开始枚举。首先枚举留下的那个集合 i i i,其次枚举这个集合有 j ( 0 ≤ j < s i z e [ i ] ) j(0\leq j < size[i]) j(0≤j<size[i])叶子被染色了。然后因为题目要求的是第一次直径减小,所以集合 i i i内部元素不能是最后一个取出的(“最后”是相对于需要被取出的 d − s i z e [ i ] + j d-size[i]+j d−size[i]+j个叶子而言),再枚举一个其他集合的节点作为排列的末尾。
所以式子已经可以大概YY出来了:
C ( s i z e [ i ] , j ) ∗ ( d − s i z e [ i ] ) ∗ ( d − s i z e [ i ] + j − 1 ) ! ∗ ( s i z e [ i ] − j ) ! / d ! ∗ ∑ k = s i z e [ i ] − j + 1 k ≤ d m k C(size[i],j)*(d-size[i])*(d-size[i]+j-1)!*(size[i]-j)!/d!*\sum_{k=size[i]-j+1}^{k \leq d}\frac{m}{k} C(size[i],j)∗(d−size[i])∗(d−size[i]+j−1)!∗(size[i]−j)!/d!∗k=size[i]−j+1∑k≤dkm
前面的组合数和阶乘就是枚举排列, ( s i z e [ i ] − j ) ! / d ! (size[i]-j)!/d! (size[i]−j)!/d!其实是 ( s i z e [ i ] − j ) ! ∗ ( m − d ) ! / ( d ! ∗ ( m − d ) ! ) (size[i]-j)!*(m-d)!/(d!*(m-d)!) (size[i]−j)!∗(m−d)!/(d!∗(m−d)!),约分就好了。
求期望的式子来源于 f [ i ] = 1 + m − i m f [ i ] f[i]=1+\frac{m-i}{m}f[i] f[i]=1+mm−if[i], f [ i ] f[i] f[i]表示还剩 i i i个是直径端点的叶子的状态,达到还剩 i − 1 i-1 i−1个是直径端点的叶子的状态需要去掉的期望步数(这么定义大概是最好理解的,然后化简一下式子就可以了)。
所以这题就这么结束了???
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e5+10;
const int mod = 998244353;
int n, s, t, l, dis[N], pnt1, pnt2, m, d;
vector<int> to[N], st;
queue<int> q;
int fact[N], invfact[N], f[N], ans;
inline int add(int x, int y){x += y; if (x >= mod) x -= mod; return x;}
inline int mul(int x, int y){return (ll)x*y%mod;} // 没用的东西
void Bfs(int s, int &t, int &l)
{
memset(dis, 0, sizeof(dis));
l = 0;
q.push(s);
while (!q.empty()){
int u = q.front();
q.pop();
if (dis[u] > l){
t = u;
l = dis[u];
}
for (int i = 0, sz = to[u].size(); i < sz; ++ i){
int v = to[u][i];
if (dis[v] || v == s) continue;
dis[v] = dis[u]+1;
q.push(v);
}
}
}
void Dfs1(int u, int fa, int &cnt, int dpt)
{
if (to[u].size() == 1){
if (dpt == l/2) ++ cnt;
++ m;
return;
}
for (int i = 0, sz = to[u].size(); i < sz; ++ i){
int v = to[u][i];
if (v == fa) continue;
Dfs1(v, u, cnt, dpt+1);
}
}
void preGao1()
{
st.resize(2, 0);
Dfs1(pnt1, pnt2, st[0], 0);
Dfs1(pnt2, pnt1, st[1], 0);
}
void preGao2()
{
st.resize(to[pnt1].size(), 0);
for (int i = 0, sz = to[pnt1].size(); i < sz; ++ i){
int v = to[pnt1][i];
Dfs1(v, pnt1, st[i], 1);
}
}
bool cmp1(int x, int y){return x > y;}
inline int Pow(int x, int y)
{
int ret = 1;
while (y){
if (y&1) ret = mul(ret, x);
x = mul(x, x);
y >>= 1;
}
return ret;
}
void preGao3()
{
invfact[0] = fact[0] = 1;
for (int i = 1; i <= m; ++ i){
fact[i] = mul(fact[i-1], i);
invfact[i] = Pow(fact[i], mod-2);
}
f[d+1] = 0;
for (int i = d; i >= 1; -- i)
f[i] = add(f[i+1], mul(m, Pow(i, mod-2)));
}
inline int C(int x, int y)
{
return 1ll*fact[x]*invfact[y]%mod*invfact[x-y]%mod;
}
int main()
{
scanf("%d", &n);
if (n == 1){
printf("1");
return 0;
}
for (int i = 1; i < n; ++ i){
int x, y;
scanf("%d%d", &x, &y);
to[x].push_back(y);
to[y].push_back(x);
}
Bfs(1, s, l);
Bfs(s, t, l);
pnt1 = t;
for (int i = 1, sz = l/2+1; i <= sz; ++ i){
for (int j = 0, sz1 = to[pnt1].size(); j < sz1; ++ j)
if (dis[to[pnt1][j]] == dis[pnt1]-1){
if (i != sz)
pnt1 = to[pnt1][j];
else
pnt2 = to[pnt1][j];
break;
}
}
if (l&1)
preGao1();
else
preGao2();
sort(st.begin(), st.end(), cmp1);
while (st.back() == 0)
st.pop_back();
for (int i = 0, sz = st.size(); i < sz; ++ i)
d += st[i];
preGao3();
ans = 0;
for (int i = 0, sz = st.size(); i < sz; ++ i)
for (int j = 0, sz1 = st[i]-1; j <= sz1; ++ j){
ans = add(ans, 1ll*C(st[i], j) *(d-st[i])%mod *fact[d-st[i]-1+j]%mod *f[st[i]-j+1]%mod *fact[st[i]-j]%mod *invfact[d]%mod);
}
printf("%d\n", ans);
return 0;
}
想在比赛的时候推出来实在是太困难了。