[XXSY] 构树(prufer序列,树上连通块DP)
傳送門
CayleyCayleyCayley公式:一個完全圖有nn?2n^{n-2}nn?2棵無根生成樹(可用prufer序列證明)
擴展CayleyCayleyCayley公式:被確定邊分為大小為a1,a2,?,ama_1,a_2,\cdots, a_ma1?,a2?,?,am?的連通塊,則有nm?2∏i=1main^{m-2}\prod\limits_{i=1}^{m}{a_i}nm?2i=1∏m?ai?種生成樹
我們強制生成樹中的一些邊與原樹中的邊相同,求出生成樹中至少有xxx條邊于原樹中邊相同的方案數ansxans_xansx?,再容斥得到生成樹中恰好有xxx條邊與原樹中邊相同的方案數AnsxAns_xAnsx?。
Ansx=∑i=x(?1)i?x(ix)ansiAns_x=\sum\limits_{i=x}(-1)^{i-x}\dbinom{i}{x}ans_iAnsx?=i=x∑?(?1)i?x(xi?)ansi?
考慮一棵以uuu為根的生成子樹,里面有jjj條邊強制與原樹中邊相同。如果只加入強制相同的jjj條邊,我們會得到若干連通塊,即子樹被非強制相同的邊分成若干連通塊,可以用拓展CayleyCayleyCayley公式統計方案數。
nm?2n^{m-2}nm?2可以留到最后處理,所以我們只考慮求∏i=1mai\prod\limits_{i=1}^{m}{a_i}i=1∏m?ai?,其組合意義為每個連通塊內各選一個關鍵點的方案數。
設dp[u][j][0/1]dp[u][j][0/1]dp[u][j][0/1]表示以uuu為根的子樹內有jjj條強制相同的邊,uuu所在的連通塊選/未選選/未選選/未選關鍵點 的選關鍵點方案數之和。轉移如下:
若u?vu-vu?v是強制相同的邊(v∈sonuv\in son_uv∈sonu?):
dp[u][j][0]×dp[v][k][0]→dp[u][j+k+1][0]dp[u][j][0]\times dp[v][k][0]\to dp[u][j+k+1][0]dp[u][j][0]×dp[v][k][0]→dp[u][j+k+1][0]
dp[u][j][0]×dp[v][k][1]+dp[u][j][1]×dp[v][k][0]→dp[u][j+k+1][1]dp[u][j][0]\times dp[v][k][1]+dp[u][j][1]\times dp[v][k][0]\to dp[u][j+k+1][1]dp[u][j][0]×dp[v][k][1]+dp[u][j][1]×dp[v][k][0]→dp[u][j+k+1][1]
若u?vu-vu?v是非強制相同的邊(v∈sonuv\in son_uv∈sonu?):
dp[u][j][0]×dp[v][k][1]→dp[u][j+k][0]dp[u][j][0]\times dp[v][k][1]\to dp[u][j+k][0]dp[u][j][0]×dp[v][k][1]→dp[u][j+k][0]
dp[u][j][1]×dp[v][k][1]→dp[u][j+k][1]dp[u][j][1]\times dp[v][k][1]\to dp[u][j+k][1]dp[u][j][1]×dp[v][k][1]→dp[u][j+k][1]
類似題目:WC2019數樹
#include<bits/stdc++.h> using namespace std; const int mod=1000000007; const int N=8010; int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f; } int add(int x,int y){return x+y>=mod?x+y-mod:x+y;} int dec(int x,int y){return x-y<0?x-y+mod:x-y;} int mul(int x,int y){return 1ll*x*y%mod;} void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;} void Dec(int &x,int y){x=x-y<0?x-y+mod:x-y;} void Mul(int &x,int y){x=1ll*x*y%mod;} int ksm(int a,int b){if(b<0) return ksm(ksm(a,-b),mod-2);int ans=1;while(b){if(b&1) ans=mul(ans,a);a=mul(a,a);b>>=1;}return ans; } int n,fac[N],ifac[N]; int cnt,head[N],nxt[N<<1],to[N<<1],siz[N]; vector<int>f[N],g[N];//f:0,g:1 void adde(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt; } void Clear(vector<int> &v){vector<int>().swap(v); } void dfs(int u,int fa){siz[u]=1;f[u].push_back(1);g[u].push_back(1);for(int i=head[u];i;i=nxt[i]){int v=to[i];if(v==fa) continue;dfs(v,u);f[u].resize(siz[u]+siz[v]);g[u].resize(siz[u]+siz[v]);for(int j=siz[u]-1;j>=0;j--){const int fu=f[u][j],gu=g[u][j];f[u][j]=g[u][j]=0;for(int k=siz[v]-1;k>=0;k--){const int fv=f[v][k],gv=g[v][k];Add(f[u][j+k+1],mul(fu,fv));Add(f[u][j+k],mul(fu,gv));Add(g[u][j+k+1],add(mul(fu,gv),mul(gu,fv)));Add(g[u][j+k],mul(gu,gv));}}siz[u]+=siz[v];Clear(f[v]),Clear(g[v]);} } int C(int n,int m){return mul(mul(fac[n],ifac[m]),ifac[n-m]); } int main(){n=read();for(int i=1;i<n;i++){int u=read(),v=read();adde(u,v),adde(v,u);}dfs(1,0);fac[0]=1;for(int i=1;i<=n;i++) fac[i]=mul(fac[i-1],i);ifac[n]=ksm(fac[n],mod-2);for(int i=n;i>=1;i--) ifac[i-1]=mul(ifac[i],i);for(int i=0;i<n;i++) Mul(g[1][i],ksm(n,n-i-2));for(int i=0;i<n;i++){int ans=0;for(int j=i;j<n;j++){if((j-i)&1) Dec(ans,mul(C(j,i),g[1][j]));else Add(ans,mul(C(j,i),g[1][j]));}printf("%d ",ans);}return 0; }總結
以上是生活随笔為你收集整理的[XXSY] 构树(prufer序列,树上连通块DP)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 台式机专业领域配置如何配台式电脑
- 下一篇: 数据保存与删除的奥秘存储过程删除数据