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

点此看题面

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

容斥

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

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

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

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

动态规划

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

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

[tmp_{x,i+j} exttt{+=}f_{x,i} imes f_{y,j}\ tmp_{x,i} exttt{+=}(-1) imes h_j imes f_{x,i} imes 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
",t),0;//最后还要算上1号点所在连通块方案
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/AT4352.html