解析
其实想到了断边按连通块容斥的做法。
但不知道为啥觉得没前途弃了…
悲。
考虑容斥,设 f[x][i] 表示 x 子树内与 x 所连的联通块有 i 个节点的方案数,第三维朴素是记录连通块个数,但其实只需要记一个 0/1 表示奇偶性即可。
然后题解奥妙重重的把这一步都取了:断奇数次贡献负,断偶数次贡献正,那直接每次断边的时候把 dp 值取反即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;
const int N=5050;
const int mod=1e9+7;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
inline ll ksm(ll x,ll k,int mod){
ll res(1);
while(k){
if(k&1) res=res*x%mod;
x=x*x%mod;
k>>=1;
}
return res;
}
int n;
ll f[N][N],tmp[2][N],top[N];
vector<int>e[N];
ll w[N];
void dfs(int x,int fa){
for(int to:e[x]){
if(to==fa) continue;
dfs(to,x);
}
memset(tmp,0,sizeof(tmp));
int now=1,pre=0,mx=1;
tmp[now][1]=1;
for(int to:e[x]){
if(to==fa) continue;
swap(now,pre);
memset(tmp[now],0,sizeof(tmp[now]));
for(int i=0;i<=top[to];i++){
for(int j=1;j<=mx;j++){
(tmp[now][i+j]+=f[to][i]*tmp[pre][j])%=mod;
}
}
mx+=top[to];
}
top[x]=mx;
for(int i=1;i<=mx;i++){
f[x][i]=tmp[now][i];
(f[x][0]+=mod-tmp[now][i]*w[i]%mod)%=mod;
}
return;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read();
w[0]=1;
for(int i=1;i<=n;i++) w[i]=(i&1)?0:w[i-2]*(i-1)%mod;
for(int i=1;i<n;i++){
int x=read(),y=read();
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
printf("%lld\n",mod-f[1][0]);
return 0;
}