正题
题目链接:
https://www.luogu.com.cn/problem/AT4352
https://atcoder.jp/contests/arc101/tasks/arc101_c
题目大意
n n n个点之间两两配对,要求配对点之间的路径覆盖整棵树,求方案数
解题思路
考虑容斥,我们钦定有 l l l条边没有路径覆盖,就有 d p dp dp状态 f i , j , l f_{i,j,l} fi,j,l表示 i i i的子树中,目前该子树的联通块大小为 j j j,已经切断了 l l l条边,我们发现该状态已经是 O ( n 3 ) O(n^3) O(n3)的,显然无法通过。
考虑优化,每个 d p dp dp状态的容斥系数是 ( − 1 ) l (-1)^l (−1)l,所以我们可以不用记录 l l l这一维度,之间用状态表示乘上了容斥系数的值。
那么我们就有
d
p
dp
dp方程,定义
c
a
l
c
(
x
)
calc(x)
calc(x)表示
x
x
x个点两两匹配的值
(
−
1
)
∗
f
x
,
i
∗
f
y
,
j
∗
c
a
l
c
(
j
)
→
f
x
,
i
(-1)*f_{x,i}*f_{y,j}*calc(j)\rightarrow f_{x,i}
(−1)∗fx,i∗fy,j∗calc(j)→fx,i
f
x
,
i
∗
f
y
,
j
→
f
x
,
i
+
j
f_{x,i}*f_{y,j}\rightarrow f_{x,i+j}
fx,i∗fy,j→fx,i+j
该方程的复杂度就是每个子树的乘积,可以理解为每个点堆之间进行一次贡献,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
c a l c ( x ) = ∏ i = 1 x − 1 [ i & 1 = = 1 ] i calc(x)=\prod_{i=1}^{x-1}[i\&1==1]i calc(x)=∏i=1x−1[i&1==1]i
c o d e code code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5100,XJQ=1e9+7;
struct node{
ll to,next;
}a[N*2];
ll n,tot,ls[N],siz[N],f[N][N],ans,g[N],fac[N];
void addl(ll x,ll y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;
}
void dp(ll x,ll fa){
siz[x]=f[x][1]=1;
for(ll i=ls[x];i;i=a[i].next){
ll y=a[i].to;
if(y==fa)continue;
dp(y,x);
for(ll i=1;i<=siz[x];i++)
g[i]=f[x][i],f[x][i]=0;
for(ll j=1;j<=siz[x];j++){
for(ll k=1;k<=siz[y];k++){
(f[x][j+k]+=f[y][k]*g[j]%XJQ)%=XJQ;
if((k&1)==0)
(f[x][j]+=XJQ-f[y][k]*g[j]%XJQ*fac[k-1]%XJQ)%=XJQ;
}
}
siz[x]+=siz[y];
}
return;
}
int main()
{
scanf("%lld",&n);
for(ll i=1;i<n;i++){
ll x,y;
scanf("%lld%lld",&x,&y);
addl(x,y);addl(y,x);
}
fac[0]=fac[1]=1;
for(ll i=2;i<=n;i++)
fac[i]=fac[i-2]*i%XJQ;
dp(1,1);
for(ll i=2;i<=n;i++)
ans=(ans+f[1][i]*fac[i-1]%XJQ)%XJQ;
printf("%lld",ans);
}