题意:
众所周知,DH是一位人生赢家,他不仅能虐暴全场,而且还正在走向人生巅峰;
在巅峰之路上,他碰到了这一题:
给出一棵
n
n
n个节点的树,我们每次随机染黑一个叶子节点(可以重复染黑),操作无限次后,这棵树的所有叶子节点必然全部会被染成黑色。
定义
R
R
R为这棵树不经过黑点的直径,求使
R
R
R第一次变小期望的步数。
数据范围:
对于
15
15
15%的数据,满足
n
≤
10
n\leq10
n≤10;
对于
30
30
30%的数据,满足
n
≤
000
n\leq000
n≤000;
另外有
20
20
20%数据,满足树为菊花图;
另外有
15
15
15%数据,满足每个点度数不超过
3
3
3;
对于
100
100
100%的数据,满足
n
≤
5
∗
1
0
5
n\leq5*10^5
n≤5∗105。
Analysis:
据我所见,这是一道极其玄学的题目:
首先我们考虑用直径性质分析一波,对于所有直径,我们可以找到一个必经点,或必经边。
我们以其为根,然后建树,端点以所属的根的子树分类,会划分成若干个集合,想要让其变小,那么最后只会剩下一个集合里面有点没被染黑。
我们可以先知道一个值,若当前有
n
n
n个点没被染黑,总点数为
m
m
m,那么再染黑一个点的期望步数为:
m
n
\frac{m}{n}
nm。
一种比较麻烦的方法是:
我们可以先把有用的叶子找出来,也就是为直径端点的叶子,这些叶子的深度在新树中为
⌊
l
e
n
2
⌋
\lfloor\frac{len}{2}\rfloor
⌊2len⌋,
l
e
n
len
len为直径长度。那么其它叶子就没用了,我们视为已经删去。设集合大小为直径端点叶子数。
第二种是老曹提供的比较简单的方法:
我们每一次枚举一个集合,使它最后删,那么对答案贡献为:
∑
i
=
1
d
0
−
d
x
m
i
\sum_{i=1}^{d_0-d_x}\frac{m}{i}
∑i=1d0−dxim。但是这样可能会出现不合法情况,即枚举的集合在其他集合删完前被删完了,那么此时贡献的是全部被染黑的贡献,这种情况会被算集合个数
−
1
-1
−1次,减去即可。
复杂度 O ( n ) O(n) O(n)。我写了第一种方法。
Code:
# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 5e5 + 5;
const int mo = 998244353;
typedef long long ll;
int st[N],to[N << 1],nx[N << 1],vis[N];
int fac[N],fac_[N],inv[N],sta[N],num[N],fa[N];
int n,m,tot,len,all,z;
inline void add(int u,int v)
{
to[++tot] = v,nx[tot] = st[u],st[u] = tot;
to[++tot] = u,nx[tot] = st[v],st[v] = tot;
}
inline int C(int n,int m) { return (ll)fac[n] * fac_[m] % mo * fac_[n - m] % mo; }
inline void dfs(int x,int s)
{
if (s > len) len = s,z = x;
for (int i = st[x] ; i ; i = nx[i])
if (to[i] != fa[x]) fa[to[i]] = x,dfs(to[i],s + 1);
}
inline void dfs1(int x,int fr,int d,int g)
{
if (d == len / 2 && vis[x]) ++num[g];
for (int i = st[x] ; i ; i = nx[i])
if (to[i] != fr) dfs1(to[i],x,d + 1,g);
}
int main()
{
freopen("winer.in","r",stdin);
freopen("winer.out","w",stdout);
scanf("%d",&n),inv[1] = fac[0] = fac_[0] = 1;
for (int i = 1 ; i <= n ; ++i)
{
if (i > 1) inv[i] = (ll)(mo - mo / i) * inv[mo % i] % mo;
fac[i] = (ll)fac[i - 1] * i % mo,fac_[i] = (ll)fac_[i - 1] * inv[i] % mo;
}
for (int i = 1 ; i < n ; ++i)
{
int u,v; scanf("%d%d",&u,&v);
add(u,v);
}
for (int i = 1 ; i <= n ; ++i) if (!nx[st[i]]) vis[i] = 1,++m;
dfs(1,0);
int fir = z; fa[fir] = len = 0,dfs(fir,0);
int sec = z; len = -1;
while (sec != fir) sta[++len] = sec,sec = fa[sec];
sta[++len] = fir;
if (len & 1)
{
dfs1(sta[len / 2],sta[len / 2 + 1],0,++all);
dfs1(sta[len / 2 + 1],sta[len / 2],0,++all);
}
else
{
int rt = sta[len / 2];
for (int i = st[rt] ; i ; i = nx[i]) dfs1(to[i],rt,1,++all);
}
int sum = 0,ans = 0;
for (int i = 1 ; i <= all ; ++i) sum += num[i];
inv[sum + 1] = 0;
for (int i = sum ; i ; --i) inv[i] = ((ll)inv[i] * m % mo + inv[i + 1]) % mo;
for (int i = 1 ; i <= all ; ++i)
for (int j = 0 ; j < num[i] ; ++j)
ans = (ll)(ans + (ll)C(num[i],j) * fac[sum - (num[i] - j + 1)] % mo * (sum - num[i]) % mo * inv[num[i] - j + 1] % mo * fac[num[i] - j] % mo) % mo;
printf("%d\n",(ll)ans * fac_[sum] % mo);
return 0;
}