把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AT4352】[ARC101C] Ribbons on Tree(容斥+DP)

点此看题面

  • 给定一棵\(n\)个点的树(\(n\)为偶数),要求将点两两配对,并给每对点间路径上的边染色。
  • 求有多少种配对方案使得所有边都被染色。
  • \(n\le5000\)

容斥

这种所有边都染色的问题很容易想到容斥。

即设至少\(k\)条边未被染色,就将这部分答案乘上容斥系数\((-1)^k\)计入最终答案中。

而一条边未被染色,说明不能跨过这条边给点配对。也就是说这\(k\)条边将把整棵树分成若干连通块,而所有的配对都只能在每个连通块内部进行。

为此,我们设出\(h_i\)表示将一个大小为\(i\)的连通块中的点两两配对的方案数,只要考虑与某一个点配对的点是谁,就能得出递推式:\(h_i=h_{i-2}\times(i-1)\)

动态规划

\(f_{x,i}\)表示执行到\(x\)的子树内,\(x\)所在连通块大小为\(i\)时的方案数,这里已经乘上了容斥系数。

从一个子节点状态\(f_{y,j}\)转移时,考虑这条边是否断开,分两种转移:

\[tmp_{x,i+j}\texttt{+=}f_{x,i}\times f_{y,j}\\ tmp_{x,i}\texttt{+=}(-1)\times h_j\times f_{x,i}\times f_{y,j} \]

由于每个点第二维上界是子树大小,可以用树上背包的记\(size\)优化保证复杂度。

代码:\(O(n^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 5000
#define X 1000000007
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,h[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
int f[N+5][N+5],g[N+5],tmp[N+5];I void DP(CI x,CI lst=0)//树形DP
{
	f[x][g[x]=1]=1;for(RI i=lnk[x],j,k;i;i=e[i].nxt) if(e[i].to^lst)
	{
		for(DP(e[i].to,x),j=1;j<=g[x];++j) for(k=1;k<=g[e[i].to];++k)//g[x]记size优化
			tmp[j+k]=(1LL*f[x][j]*f[e[i].to][k]+tmp[j+k])%X,//不断这条边
			tmp[j]=((X-1LL)*h[k]%X*f[x][j]%X*f[e[i].to][k]+tmp[j])%X;//断开这条边
		for(g[x]+=g[e[i].to],j=1;j<=g[x];++j) f[x][j]=tmp[j],tmp[j]=0;//把临时数组中的值转移到f中
	}
}
int main()
{
	RI i,x,y;for(scanf("%d",&n),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	for(h[0]=1,i=2;i<=n;i+=2) h[i]=1LL*h[i-2]*(i-1)%X;//预处理每种大小连通块两两配对方案
	RI t=0;for(DP(1),i=1;i<=n;++i) t=(1LL*h[i]*f[1][i]+t)%X;return printf("%d\n",t),0;//最后还要算上1号点所在连通块方案
}
posted @ 2021-05-07 20:10  TheLostWeak  阅读(88)  评论(0编辑  收藏  举报