1. 程式人生 > 其它 >Codeforces 917D - Stranger Trees(矩陣樹定理/推式子+組合意義)

Codeforces 917D - Stranger Trees(矩陣樹定理/推式子+組合意義)

Codeforces 題目傳送門 & 洛谷題目傳送門

剛好看到 wjz 在做這題,心想這題之前好像省選前做過,當時覺得是道挺不錯的題,為啥沒寫題解呢?於是就過來補了,由此可見我真是個大鴿子((

跑題了跑題了……

這裡提供兩種解法:

Algorithm 1.

注意到“恰好”二字有點藍瘦,因此套路地想到二項式反演,也就說我們欽定 \(k\) 條邊必須與原樹中的邊重合,其餘邊可以隨便連的方案數,我們假設這些與原樹中的邊重合的邊構成的集合為 \(E'\),那麼 \(E'\) 中顯然包含了一些連通塊,而我們的任務就是在這些連通塊之間連上一些邊,使其成為一棵樹,我們假設 \(E'\) 中的點組成了 \(r\)

個連通塊,大小分別為 \(a_1,a_2,\cdots,a_r\),那麼這裡就有一個大家熟知,而我不知道的定理:構成的生成樹個數為 \(n^{r-2}\prod\limits_{i=1}^ra_i\)

證明:自己瞎 yy 的,證明錯了不要打我(大霧

就是考慮 Prufer 序列,我們假設 \(i\) 在 Prufer 序列中出現的次數為 \(p_i\),那麼可以列出柿子:

\[\begin{aligned} |T|&=\sum\limits_{\sum p_i=r-2}\prod\limits_{i=1}^ra_i^{p_i+1}\times\dbinom{r-2}{p_1,p_2,\cdots,p_r}\\ &=\sum\limits_{\sum p_i=r-2}(r-2)!\prod\limits_{i=1}^r\dfrac{a_i^{p_i+1}}{p_i!}\\ &=\prod\limits_{i=1}^ra_i\times(r-2)!\sum\limits_{\sum p_i=r-2}\prod_{i=1}^r\dfrac{a_i^{p_i}}{p_i!} \end{aligned} \]

注意到後面那坨東西就是 \(\prod\limits_{i=1}^r[x^{p_i}]e^{a_i}\)

,因此後面 \(\sum\) 裡面的東西可以改寫為 \(\sum\limits_{\sum p_i=n-2}\prod\limits_{i=1}^r[x^{p_i}]e^{a_i}=[x^{n-2}]\prod\limits_{i=1}^re^{a_i}=[x^{r-2}]e^n=n^{r-2}·\dfrac{1}{(r-2)!}\),故 \(|T|=\prod\limits_{i=1}^ra_i\times(r-2)!\times n^{r-2}\times(r-2)!=n^{r-2}\prod\limits_{i=1}^ra_i\)

大功告成。

注意到當我們列舉重合邊數 \(k\) 時,\(r=n-k\)

,因此前面 \(n^{r-2}\) 是常量,我們只需考慮後面的東西即可,而後面的東西有著清晰的組合意義:將原樹分成 \(r\) 個連通塊,每個連通塊裡恰好放一個球的方案數,因此考慮樹形 \(dp\)\(dp_{u,j,0/1}\) 表示 \(u\) 的子樹內劃分為 \(j\) 個連通塊,\(u\) 所在的連通塊是否放了球的方案數。按照樹上揹包的套路合併即可,具體來說,如果我們要合併以 \(x,y\) 為根的子樹,其中 \(x\) 為父親,那麼:

  • \(dp_{x,i+j,0}\leftarrow dp_{x,i,0}\times dp_{y,j,1}\)
  • \(dp_{x,i+j,1}\leftarrow dp_{x,i,1}\times dp_{y,j,1}\)
  • \(dp_{x,i+j-1,0}\leftarrow dp_{x,i,0}\times dp_{y,j,0}\)
  • \(dp_{x,i+j-1,1}\leftarrow dp_{x,i,0}\times dp_{y,j,1}+dp_{x,i,1}\times dp_{y,j,0}\)

時間複雜度 \(n^2\)

const int MAXN=100;
const int MOD=1e9+7;
int qpow(int x,int e){
	if(e<0) e+=MOD-1;int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
int fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
	fac[0]=ifac[0]=ifac[1]=1;
	for(int i=2;i<=n;i++) ifac[i]=1ll*(MOD-MOD/i)*ifac[MOD%i]%MOD;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
int binom(int x,int y){return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;}
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dp[MAXN+5][MAXN+5][2],siz[MAXN+5],tmp[MAXN+5][2];
int f[MAXN+5],g[MAXN+5];
void dfs(int x,int f){
	dp[x][1][0]=dp[x][1][1]=siz[x]=1;
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;dfs(y,x);memset(tmp,0,sizeof(tmp));
		for(int i=1;i<=siz[x];i++) for(int j=1;j<=siz[y];j++){
			tmp[i+j][0]=(tmp[i+j][0]+1ll*dp[x][i][0]*dp[y][j][1])%MOD;
			tmp[i+j][1]=(tmp[i+j][1]+1ll*dp[x][i][1]*dp[y][j][1])%MOD;
			tmp[i+j-1][0]=(tmp[i+j-1][0]+1ll*dp[x][i][0]*dp[y][j][0])%MOD;
			tmp[i+j-1][1]=(tmp[i+j-1][1]+1ll*dp[x][i][1]*dp[y][j][0]+1ll*dp[x][i][0]*dp[y][j][1])%MOD;
		} siz[x]+=siz[y];
		for(int i=1;i<=siz[x];i++) dp[x][i][0]=tmp[i][0],dp[x][i][1]=tmp[i][1];
	}
//	for(int i=1;i<=siz[x];i++) printf("%d %d %d %d\n",x,i,dp[x][i][0],dp[x][i][1]);
}
int main(){
	scanf("%d",&n);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	dfs(1,0);init_fac(n);
	for(int i=1;i<=n;i++) f[n-i]=1ll*dp[1][i][1]*qpow(n,i-2)%MOD;
	for(int i=0;i<n;i++) for(int j=i;j<n;j++){
		if((j-i)&1) g[i]=(g[i]-1ll*f[j]*binom(j,i)%MOD+MOD)%MOD;
		else g[i]=(g[i]+1ll*f[j]*binom(j,i))%MOD;
	}
	for(int i=0;i<n;i++) printf("%d%c",g[i],(i==n-1)?'\n':' ');
	return 0;
}

Algorithm 2.

看到“生成樹個數”,當然想到矩陣樹定理咯(廢話

但是本題一開始的樹是沒有權值的,因此我們要給這棵樹賦上恰當的權值。注意到題目相當於從這 \(n\) 個點的完全圖中找出一些生成樹,對於生成樹中的每條邊,如果它也在原樹中,那麼會對重複邊數產生 \(1\) 的貢獻,否則對重複邊數產生 \(0\) 的貢獻。因此考慮建一張由這 \(n\) 個點組成的完全圖,對於所有在原樹中的邊賦上權值 \(x\),其餘的邊賦上權值 \(1\),那麼顯然所有生成樹權值乘積之和是一個多項式 \(P\),而 \(ans_k\) 就是 \([x^k]P\),這個很好理解。

不過如果我們暴力使用多項式進行計算,那複雜度將會高達 \(n^5\)不知道能不能過得去,因此考慮使用解多項式題的常用技巧——插值。根據 \(n+1\) 個點唯一確定一個 \(n\) 次多項式這個性質,我們考慮令 \(x=1,2,\cdots,n\) 分別跑一遍矩陣樹定理,然後高斯消元解出係數即可。當然如果你閒著無聊寫個拉格朗日插值或者更快的插值方法那我也不攔著你,瓶頸也不在從 \(n\) 個點值求出係數處/cy

時間複雜度 \(n^4\)

程式碼?抱歉,當時太菜了不會 Matrix-Tree 定理/wq,所以程式碼就咕著了(((