分析:
很不错的树形DP+容斥原理的混合题
不得不说,尽管Atcoder去世了一个半月,但这次题目质量仍然保持得很不错
首先,这题可以运用容斥原理转化一下:定义EE为给出的边集,设 ,f(F)f(F)表示:F中的边全部未被覆盖的方案数。
答案就是∑(?1)|F|f(F)∑(?1)|F|f(F)
那么接下来就是求f(F)f(F)
其实这个很显然,考虑在树上去除所有F中的边,那么每次选择的点对,都必须在剩下的图中的同一个联通块内。这样的联通块必定有|F|+1|F|+1个,设其点数分别为n1,n2,n3…n|F|+1n1,n2,n3…n|F|+1
设一个函数g(n)g(n)表示在n个点中选出点两两一组的方案数。
那么很显然当n为奇数时,g(n)=0g(n)=0
否则g(n)=(n?1)?(n?3)?……?3?1g(n)=(n?1)?(n?3)?……?3?1
那么f(F)=g(n1)?g(n2)?g(n3)……g(n|F|+1)f(F)=g(n1)?g(n2)?g(n3)……g(n|F|+1)
现在考虑快速地求f(F)f(F),这时树形DP就派上用场了。
定义dp(x,sum)dp(x,sum)表示以x为根的子树中,和x的父亲相连的点有sum个时的方案数。
转移也就很明显了,依次遍历每个亲儿子,暴力枚举在这个儿子的子树中选kk个点的方案数。
当sum=0时,就相当于和父亲的边在F中,这种情况的转移比较特殊,因为此时相当于一个联通块被考虑完了,所以要枚举所有可能的联通块大小,并且由于 多了这一条边,所以要全部取相反数,dp(x,0)=?∑dp(x,sum′)?f(sum′)dp(x,0)=?∑dp(x,sum′)?f(sum′)。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 5010
#define MOD 1000000007
using namespace std;
typedef long long ll;
ll dp[MAXN][MAXN],f[MAXN],t[MAXN];
int siz[MAXN];
vector<int> a[MAXN];
void dfs(int x,int fa){siz[x]=1;dp[x][1]=1;for(int i=0;i<a[x].size();i++){int u=a[x][i];if(u==fa)continue;dfs(u,x);for(int j=0;j<=siz[x];j++){t[j]=dp[x][j];dp[x][j]=0;}for(int j=0;j<=siz[x];j++)for(int k=0;k<=siz[u];k++)(dp[x][j+k]+=t[j]*dp[u][k]%MOD)%=MOD;siz[x]+=siz[u];}for(int i=0;i<=siz[x];i+=2)dp[x][0]=(dp[x][0]-dp[x][i]*f[i]%MOD+MOD)%MOD;
}
int n,u,v;
int main(){SF("%d",&n);for(int i=1;i<n;i++){SF("%d%d",&u,&v);a[u].push_back(v);a[v].push_back(u);} f[0]=1;for(int i=2;i<=n;i+=2)f[i]=f[i-2]*(i-1)%MOD;dfs(1,0);PF("%lld",MOD-dp[1][0]);
}