UOJ #351.新年的叶子(数学题?)

题意

给一棵树,每次可以染色一个叶子(只能是原树上的叶子,且每个叶子可以染色多次),求使不经过被染色的节点的直径减小的期望步数。

思路

可能会有点长,大概的理一理。

一、求直径

nothing to say。走流程。

二、分集合

然后开始观察这棵树。直径可能有多条。然后我们回顾一下直径的一些性质:

  1. 所有直径一定交于连续的一段
  2. 直径的交集一定包含每条直径的中心(边或点)

证明略。

那么如何可以减小一棵树的直径的长度呢?感性理解,肯定要把所有直径都至少削掉一个叶子。那么是不是可以把直径的端点分成一些集合,使得每个集合内部两点连线无法形成直径。所以现在问题变成了去掉一些元素使得只剩下一个集合。

那么如何分集合呢?我们分直径长度的奇偶性考虑。

长度奇数,则有一条处于正中间的边,被所有直径经过。把这条边剖开,两边的端点都无法形成直径,那么就此可以分成2个集合。

长度偶数,则有一个点处在中间。同理,去掉这个点,所有直径都断了,所以对于中间点的所有子树,每个子树包含的直径端点构成一个集合。

好了,问题已经解决一半了。

三、统计答案

总体思路:可以把叶子被染色的顺序看成一个排列,累加每个排列的期望值,再除以总排列数即总答案。

假设 m m m表示总叶子数, d d d表示是直径端点的叶子数。

开始枚举。首先枚举留下的那个集合 i i i,其次枚举这个集合有 j ( 0 ≤ j &lt; s i z e [ i ] ) j(0\leq j &lt; size[i]) j(0j<size[i])叶子被染色了。然后因为题目要求的是第一次直径减小,所以集合 i i i内部元素不能是最后一个取出的(“最后”是相对于需要被取出的 d − s i z e [ i ] + j d-size[i]+j dsize[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)(dsize[i])(dsize[i]+j1)!(size[i]j)!/d!k=size[i]j+1kdkm

前面的组合数和阶乘就是枚举排列, ( 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)!(md)!/(d!(md)!),约分就好了。

求期望的式子来源于 f [ i ] = 1 + m − i m f [ i ] f[i]=1+\frac{m-i}{m}f[i] f[i]=1+mmif[i] f [ i ] f[i] f[i]表示还剩 i i i个是直径端点的叶子的状态,达到还剩 i − 1 i-1 i1个是直径端点的叶子的状态需要去掉的期望步数(这么定义大概是最好理解的,然后化简一下式子就可以了)。



所以这题就这么结束了???



代码

#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;
}

想在比赛的时候推出来实在是太困难了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值