[ARC101C] Ribbons on Tree 解题报告
给出一棵 \(n\) 个点的树,要求将树上的节点两两配对,对于一组配对 \((u,v)\) ,将 \(u\) 到 \(v\) 的路径染色,求所有边都被染上色的方案数,答案对 \(10^9+7\) 取模。
\(n\le 5005\) ,保证 \(n\) 为偶数。
这题和氪金手游那题很类似,假设 \(f(S)\) 是在集合 \(S\) 中的边都没有被染色的方案数,其中 \(S\in E\) ,那么答案为 \(\sum (-1)^{|S|} f(S)\) 。
将一定不染色的边删去,一棵树就会被分为许多联通块,大小为 \(n\) 的联通块的贡献是所有配对方案的个数,也就是 \(\frac{\frac{n(n-1)}{2}\times\frac{(n-2)(n-3)}{2}\times...\times \frac{2\times 1}{2}}{(\frac{n}{2})!}=(n-1)\times(n-3)\times...\times 1\) ,其中 \(n\) 为偶数,否则不能匹配,那么有一个简单的三维 \(dp_{i,j,k}\) ,代表在 \(i\) 的子树中, \(i\) 所在联通块的大小为 \(j\) ,删去了 \(k\) 条边的方案数,转移就显而易见了。
类似氪金手游那题,可以将删去的边数这一维优化掉,因为多删一条边就会整体左移一位,放在容斥中就是整体乘上 \(-1\) ,可以在 \(j=0\) 也就是删去 \(i\) 到父亲的那条边时统计的答案乘上 \(-1\) 。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M=5005,JYY=1e9+7;
int read(){
int x=0,y=1;char ch=getchar();
while(ch<'0'||ch>'9') y=(ch=='-'?-1:1),ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*y;
}
int tot=0,first[M];
struct Edge{ int nxt,to; }e[M<<1];
void add(int x,int y){
e[++tot]=(Edge){first[x],y};
first[x]=tot;
}
int dp[M][M],sz[M],tmp[M],gs[M];
void dfs(int u,int fa){
dp[u][1]=sz[u]=1;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa) continue ;
dfs(v,u);
for(int j=1;j<=sz[u];j++) for(int k=0;k<=sz[v];k++)
tmp[j+k]=(tmp[j+k]+dp[u][j]*dp[v][k]%JYY)%JYY;
sz[u]+=sz[v];
for(int j=1;j<=sz[u];j++) dp[u][j]=tmp[j],tmp[j]=0;
}
for(int i=2;i<=sz[u];i+=2) dp[u][0]=(dp[u][0]-gs[i]*dp[u][i]%JYY+JYY)%JYY;
}
int n,Ans=0;
void solve(){
n=read();gs[0]=1;
for(int i=2;i<=n;i+=2) gs[i]=gs[i-2]*(i-1)%JYY;
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y),add(y,x);
}
dfs(1,0);
printf("%lld\n",JYY-dp[1][0]);
}
signed main(){
// freopen("shuju.in","r",stdin);
solve();
}