UOJ #351.新年的葉子(數學題?)
阿新 • • 發佈:2018-12-19
題意
給一棵樹,每次可以染色一個葉子(只能是原樹上的葉子,且每個葉子可以染色多次),求使不經過被染色的節點的直徑減小的期望步數。
思路
可能會有點長,大概的理一理。
一、求直徑
nothing to say。走流程。
二、分集合
然後開始觀察這棵樹。直徑可能有多條。然後我們回顧一下直徑的一些性質:
- 所有直徑一定交於連續的一段
- 直徑的交集一定包含每條直徑的中心(邊或點)
證明略。
那麼如何可以減小一棵樹的直徑的長度呢?感性理解,肯定要把所有直徑都至少削掉一個葉子。那麼是不是可以把直徑的端點分成一些集合,使得每個集合內部兩點連線無法形成直徑。所以現在問題變成了去掉一些元素使得只剩下一個集合。
那麼如何分集合呢?我們分直徑長度的奇偶性考慮。
長度奇數,則有一條處於正中間的邊,被所有直徑經過。把這條邊剖開,兩邊的端點都無法形成直徑,那麼就此可以分成2個集合。
長度偶數,則有一個點處在中間。同理,去掉這個點,所有直徑都斷了,所以對於中間點的所有子樹,每個子樹包含的直徑端點構成一個集合。
好了,問題已經解決一半了。
三、統計答案
總體思路:可以把葉子被染色的順序看成一個排列,累加每個排列的期望值,再除以總排列數即總答案。
假設表示總葉子數,表示是直徑端點的葉子數。
開始列舉。首先列舉留下的那個集合,其次列舉這個集合有葉子被染色了。然後因為題目要求的是第一次直徑減小,所以集合內部元素不能是最後一個取出的(“最後”是相對於需要被取出的個葉子而言),再列舉一個其他集合的節點作為排列的末尾。
所以式子已經可以大概YY出來了:
前面的組合數和階乘就是列舉排列,其實是,約分就好了。
求期望的式子來源於,表示還剩個是直徑端點的葉子的狀態,達到還剩個是直徑端點的葉子的狀態需要去掉的期望步數(這麼定義大概是最好理解的,然後化簡一下式子就可以了)。
所以這題就這麼結束了???
程式碼
#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;
}
想在比賽的時候推出來實在是太困難了。