1. 程式人生 > >UOJ #351.新年的葉子(數學題?)

UOJ #351.新年的葉子(數學題?)

題意

給一棵樹,每次可以染色一個葉子(只能是原樹上的葉子,且每個葉子可以染色多次),求使不經過被染色的節點的直徑減小的期望步數。

思路

可能會有點長,大概的理一理。

一、求直徑

nothing to say。走流程。

二、分集合

然後開始觀察這棵樹。直徑可能有多條。然後我們回顧一下直徑的一些性質:

  1. 所有直徑一定交於連續的一段
  2. 直徑的交集一定包含每條直徑的中心(邊或點)

證明略。

那麼如何可以減小一棵樹的直徑的長度呢?感性理解,肯定要把所有直徑都至少削掉一個葉子。那麼是不是可以把直徑的端點分成一些集合,使得每個集合內部兩點連線無法形成直徑。所以現在問題變成了去掉一些元素使得只剩下一個集合。

那麼如何分集合呢?我們分直徑長度的奇偶性考慮。

長度奇數,則有一條處於正中間的邊,被所有直徑經過。把這條邊剖開,兩邊的端點都無法形成直徑,那麼就此可以分成2個集合。

長度偶數,則有一個點處在中間。同理,去掉這個點,所有直徑都斷了,所以對於中間點的所有子樹,每個子樹包含的直徑端點構成一個集合。

好了,問題已經解決一半了。

三、統計答案

總體思路:可以把葉子被染色的順序看成一個排列,累加每個排列的期望值,再除以總排列數即總答案。

假設mm表示總葉子數,dd表示是直徑端點的葉子數。

開始列舉。首先列舉留下的那個集合ii,其次列舉這個集合有j(0j<size[i])j(0\leq j < size[i])

size[i])葉子被染色了。然後因為題目要求的是第一次直徑減小,所以集合ii內部元素不能是最後一個取出的(“最後”是相對於需要被取出的dsize[i]+jd-size[i]+j個葉子而言),再列舉一個其他集合的節點作為排列的末尾。

所以式子已經可以大概YY出來了:

C(size[i],j)(dsize[i])(dsize[i]+j1)!(size[i]j)!/d!k=size[i]j+1kdmkC(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}

前面的組合數和階乘就是列舉排列,(size[i]j)!/d!(size[i]-j)!/d!其實是(size[i]j)!(md)!/(d!(md)!)(size[i]-j)!*(m-d)!/(d!*(m-d)!),約分就好了。

求期望的式子來源於f[i]=1+mimf[i]f[i]=1+\frac{m-i}{m}f[i]f[i]f[i]表示還剩ii個是直徑端點的葉子的狀態,達到還剩i1i-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;
}

想在比賽的時候推出來實在是太困難了。