標籤:and 子節點 out 相互 表示 ble print gis 如何
description
codeforces
給一棵\(n\)個節點的樹,每次等機率選擇樹中剩下邊的一條進行縮邊,這條邊的兩個端點有相同的機率被保留,求最後每個點被留下的機率。
data range
\[n\le 50\]
solution
感謝Mr_Spade的部落格教會了我做這道題。
考慮一下這個縮邊的過程:依次選擇\(n-1\)條邊進行縮邊,縮邊時可以選擇這條邊串連的兩條節點中的任意一個保留,最後只剩下一個節點。
可以發現在這個過程中我們並不知道最後剩下的到底是哪個節點,因此我們選擇欽定最後留下的節點,每次有和它相連的邊進行收縮時總是它保留下來,問題轉化為求欽定的一個節點最後留下的機率。
另外,這個過程的方案數是有限的,可以知道是\(2^{n-1}(n-1)!\)。那麼這道題實際上變成了一道計數問題,我們只要求出使欽定的一個節點最後留下的方案數,最後再除以總方案數即可。
將欽定最後留下的點看成整棵樹的根節點,我們考慮樹形\(DP\)。
子樹的狀態應該是忽略子樹外的所有邊,當前子樹的根節點保留下來的方案數,那麼我們可以知道由子樹轉移到父親過程的表現如下:
1.根節點轉移到父親節點(父親節點被根節點所替代);
2.在縮掉父親節點和兒子節點之間的連邊之前,縮掉了一些其他的邊;
3.縮掉了父親節點和兒子節點之間的連邊,根節點轉移到兒子節點。
由子樹轉移到父親的過程就是已知到達步驟3的方案數推知到達步驟1的方案數的過程。
由於在根節點轉移到父親節點後,我們還有可能縮掉一些其他的邊,
可以知道縮掉其他邊的方案數 和 在根節點轉移到父親節點後,父節點剩下的邊數有關
那麼我們顯然需要在記錄子數的基礎上再記錄一維表示邊數。
設\(f_{u,i}\)表示根節點\(rt\)轉移到節點\(u\),子數\(u\)內還剩下\(i\)條邊的方案數,答案即\(f_{rt,n-1}\)。
這個狀態實際上包含了兩種邊排列的方案:一類是此時已經縮掉的邊,一類是此時還剩下的\(i\)條邊。
枚舉其兒子節點\(v\),如何添加新子樹\(v\)?
對於一個完整的根節點從\(u->v\)的過程,我們還要知道在根節點轉移到\(v\)時\(v\)子樹內剩下的邊數\(j\)。
我們已經考慮好了縮掉前面子樹中邊的情況,因此只需要考慮步驟2在子樹\(v\)中究竟縮掉了多少條邊。
設這個值為\(k\),那麼根節點轉移到\(u\)時\(v\)子樹內剩下的邊數為\(j+k\)。
設\(f‘_u\)為轉移後的數組\(f_u\),枚舉\(i,j,k\),我們進行一下分類討論:
\(k=0:\)根節點轉移到\(u\)時不需要縮邊即轉移到根節點\(v\),說明\(u\)和\(v\)在根節點到達\(u\)之前就已經縮為一點。
\((u,v)\)這條邊只能插入\(v\)的子樹已經被縮掉的方案中,它在方案中可以放到最前或在第\((sz_v-1-i)\)條被縮掉的邊之後(\(sz_v\)表示\(v\)的子樹大小),因此插入這條邊的方案數為\((sz_v-i)\);注意這條邊可以隨意選縮掉了哪個節點,於是方案數\(\times 2\);
然後考慮將\(v\)的子樹的縮邊方案合并到\(u\)的子樹的縮邊方案,注意只能是已經縮掉的邊相互合并,還未縮掉的邊相互合并,於是方案數為\(\binom{i+j}{j}\binom{sz_u-i-1+sz_v-j}{sz_v-j}\)。
轉移方案即\(f‘_{u,i+j+k+1}+=f_{u,i}\times 2(sz_v-j)\binom{i+j}{j}\binom{sz_u-i-1+sz_v-j}{sz_v-j}f_{v,j}\)
\(k>0:\)根節點轉移到\(u\)後繼續在子樹\(v\)中縮了\(k\)條邊,最後轉移到根節點\(v\)。
注意\((u,v)\)這條邊的縮邊時間和選擇保留的點都已經被欽定(在繼續縮\(k\)條邊之後,只能保留根節點),因此直接使用\(f‘_{u,i+j+k+1}+=f_{u,i}\times\binom{i+j}{j}\binom{sz_u-i-1+sz_v-j}{sz_v-j}f_{v,j}\)轉移方案。
實現時可以存一個\(g_i\)表示\(DP\ f_{u,i}\)後乘的一大堆東西,這樣總複雜度為\(O(n^4)\)。
Code
#include<bits/stdc++.h>#define FILE "CF1060F"#define mp make_pair#define pb push_back#define RG register#define il inlineusing namespace std;typedef unsigned long long ull;typedef vector<int>VI;typedef long long ll;typedef double dd;const int N=52;il ll read(){ RG ll data=0,w=1;RG char ch=getchar(); while(ch!=‘-‘&&(ch<‘0‘||ch>‘9‘))ch=getchar(); if(ch==‘-‘)w=-1,ch=getchar(); while(ch<=‘9‘&&ch>=‘0‘)data=data*10+ch-48,ch=getchar(); return data*w;}il void file(){ srand(time(NULL)+rand()); freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout);}int n,head[N],nxt[N<<1],to[N<<1],cnt;il void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}int sz[N];dd fac[N],f[N][N],g[N],tmp[N];il dd C(int n,int m){return fac[n]/fac[m]/fac[n-m];}void dfs(int u,int ff){ f[u][0]=sz[u]=1; for(RG int i=head[u];i;i=nxt[i]){ RG int v=to[i];if(v==ff)continue; dfs(v,u);memset(g,0,sizeof(g)); for(RG int j=0;j<=sz[v];j++) for(RG int k=0;k<=j;k++) if(j>k)g[j]+=f[v][k]; else if(j==k)g[j]+=2*(sz[v]-j)*f[v][k]; memset(tmp,0,sizeof(tmp)); for(RG int j=0;j<sz[u];j++) for(RG int k=0;k<=sz[v];k++) tmp[j+k]+=f[u][j]*g[k]*C(j+k,j)*C(sz[u]-j-1+sz[v]-k,sz[u]-j-1); memcpy(f[u],tmp,sizeof(f[u])); sz[u]+=sz[v]; }}int main(){ n=read();fac[0]=1; for(RG int i=1;i<=n;i++)fac[i]=fac[i-1]*i*2; for(RG int i=1,u,v;i<n;i++){ u=read();v=read();add(u,v);add(v,u); } for(RG int i=1;i<=n;i++){ memset(f,0,sizeof(f)); dfs(i,0); printf("%.10lf\n",f[i][n-1]/fac[n-1]); } return 0;}
[CF1060F]Shrinking Tree