ARC101E Ribbons on Tree 树形dp 容斥

17 篇文章 0 订阅
11 篇文章 0 订阅

题目链接

题意:
给你一棵 n n n个点的树, n n n是偶数,把这些点分成 n 2 \frac{n}{2} 2n个点对,每个点对会把路径上的所有边覆盖,问你每条边至少覆盖一次有多少种配对方式。 n &lt; = 5000 n&lt;=5000 n<=5000

题解:
又是我看题解都半天看不懂的题。算是一道比较神仙的计数题。题解上来就说考虑容斥,结果我很久才明白那个容斥是在干什么。感觉现在也不是很明白,所以可能题解中有诸多错误,还请斧正。

做法是这样的,我们发现,如果给你一棵树,你让里面的点随意两两配对,这个方案数是可以算的,我们记作 g g g。计算的方法是,显然只有点数为偶数时才会有合法的配对方案,这个方案数是 ( n − 1 ) ∗ ( n − 3 ) ∗ . . . ∗ 1 (n-1)*(n-3)*...*1 (n1)(n3)...1,原因是一开始随便找一个点,有 ( n − 1 ) (n-1) (n1)个点可以和它配对,由于选其他的点出来时的最后配对方案会与当前随便找的这个点的某个方案重复,也就是这时候选出的每一个点都是等价的,所以并不去乘 n n n,下只有 n − 2 n-2 n2个点没有用过了,那么我们再拿出一个,就有 n − 3 n-3 n3种可能。以此类推,就可以得到求 g g g的这个式子,所以我们就可以把 g g g给预处理出来。

我们设 f ( S ) f(S) f(S) S S S这个集合的边都没有被覆盖到,并且每个连通块内部随意配对的方案数,我们可以发现,这些没有被覆盖的边把树划分成了 ∣ S ∣ + 1 |S|+1 S+1个连通块,为了保证这些边不被覆盖,每个连通块只能内部配对,所以我们令 f ( S ) = ∏ i = 1 ∣ S ∣ + 1 g ( s i z e [ i ] ) f(S)=\prod_{i=1}^{|S|+1}g(size[i]) f(S)=i=1S+1g(size[i]),其中 s i z e size size表示连通块大小。

根据容斥原理,我们要求的答案就是 ∑ S ⊆ E ( − 1 ) ∣ S ∣ f ( S ) \sum_{S\subseteq E}(-1)^{|S|}f(S) SE(1)Sf(S)。我按我的理解来解释一下这个式子,就是利用容斥,我们用所有都随意配对的方案数,减去只有一条边没有覆盖,其余随意配对的方案数,此时有两条边没有覆盖的那些方案会在那两条边的地方各减去一次,所以要加回来一次,也就是加上有两条边没有被覆盖,其余随便配对的方案,以此类推。

我们不能直接暴力枚举 S S S算答案,我们考虑用树形dp来算出最终答案。我们设 d p [ x ] [ i ] dp[x][i] dp[x][i]为在 x x x这个点,还有 i i i个子树内的点没有匹配的方案数。我们转移时枚举子树,先算出新的dp值,再更新当前点的size,算dp值的时候枚举之前还剩多少点没有匹配,新的子树内又提供了多少没有匹配的点。我们需要特别处理 d p [ x ] [ 0 ] dp[x][0] dp[x][0],因为这时候所有点都在子树内部匹配了,我们枚举已经处理好了所有不超过 s i z e [ x ] size[x] size[x]偶数,统计答案时要乘上一个 g g g,表示那种情况下的所有方案,还要再乘一个 − 1 -1 1,表示所有点在子树内相互匹配了,所以当前点到这棵子树的这条边就一定没法被覆盖了,比之前又多了一条没被覆盖的边,容斥系数变为原来的相反数。我们设根节点是1号节点,最后答案就是 d p [ 1 ] [ 0 ] ∗ ( − 1 ) dp[1][0]*(-1) dp[1][0](1)。这里乘 − 1 -1 1的原因我感觉是1号点是根,没有父节点了,也就不会多出一条没被覆盖的边,但是在计算的时候还是按照有多出了一条没被覆盖的边计算的,所以最后理应不乘-1,这里再乘一次-1,就变回应有的结果了。我感觉这个树形dp的本质应该是在对于没有覆盖的边数进行容斥,并不是在按点对的配对情况dp。

总之我是真的看不懂各位神仙写的题解啊,可能过两天还会再研究一下。

update:
请教了chaojidouding大神这个题的做法,感觉他的做法与我之前写的稍有不同,但是好理解不少。
他的做法设 d p [ x ] [ i ] [ 0 / 1 ] dp[x][i][0/1] dp[x][i][0/1]表示在 x x x的子树内,有 i i i个点没有被匹配,有偶数/奇数条边没有被覆盖的方案数(因为容斥系数只与边数的奇偶性有关)。dp转移的方法和我类似,他的写法是最后再乘 g g g,然后在算完之后统计根节点的答案。

放两个他的核心代码的片段(完整版是可以在atcoder上找到的):
代码
在这里插入图片描述

我的做法的代码:

#include <bits/stdc++.h>
using namespace std;

int n,hed[5010],sz[5010],cnt;
long long f[5010][5010],g[5010],dp[5010];
const long long mod=1e9+7;
struct node
{
 int to,next;
}a[200010];
inline void add(int from,int to)
{
 a[++cnt].to=to;
 a[cnt].next=hed[from];
 hed[from]=cnt;
}
inline void dfs(int x,int fa)
{
 sz[x]=1;
 f[x][1]=1;
 for(int i=hed[x];i;i=a[i].next)
 {
  int y=a[i].to;
  if(y==fa)
  continue;
  dfs(y,x);
  for(int j=0;j<=sz[x]+sz[y];++j)
  dp[j]=0;
  for(int j=1;j<=sz[x];++j)
  {
   for(int k=0;k<=sz[y];++k)
   {
    dp[j+k]=(dp[j+k]+f[x][j]*f[y][k]%mod)%mod;
   }
  }
  sz[x]+=sz[y];
  for(int j=1;j<=sz[x];++j)
  f[x][j]=dp[j];
 }
 for(int i=2;i<=sz[x];i+=2)
 f[x][0]=(f[x][0]+mod-f[x][i]*g[i]%mod)%mod;
}
int main()
{
 scanf("%d",&n);
 for(int i=1;i<=n-1;++i)
 {
  int x,y;
  scanf("%d%d",&x,&y);
  add(x,y);
  add(y,x);
 }
 g[0]=1;
 for(int i=2;i<=n;i+=2)
 g[i]=g[i-2]*(i-1)%mod;
 dfs(1,0);
 printf("%lld\n",(mod-f[1][0])%mod);
 return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值