ARC101E题解

前言

此片题解大致按照笔者做题思路进行讲解。

简要题意

有一棵树,树上有偶数个节点。你需要给这些点两两配对,一组已经配对的点会将两点之间的树边进行一次覆盖。一组合法方案需要满足树上所有边都被覆盖至少一次,求合法方案数。

数据范围n5000

思路

首先我们去观察题目性质,发现没有什么特殊的地方。我最开始只想到一个非常暴力的 dp,设 fu,i 表示以 u 为根的子树内有 i 个点已经匹配好的方案数。但是当我去考虑转移时,我发现他有很多种情况:

  1. u 一个儿子的子树内互相匹配,但是需要有一个点与外面的点匹配(不然这个子树与 u 之间的边就无法被覆盖);
  2. 一个子树内的点向 u 的其他子树匹配;
  3. 一个子树的点向 u 子树以外的点匹配。

或许还有一些没有罗列出来,但反正就是不可做。于是我们正难则反,考虑先求出不合法的情况,然后容斥做。

题解

如何求不合法的情况呢?我们可以通过钦定一些边不覆盖来容斥。比如当我计算到以 u 为根的子树时,我就去枚举 u 所在的连通块的大小,对于一个 u 的儿子 v,分讨一下连通块是否包括 v

具体的,我们设 fu,i 表示以 u 为根的子树,u 所在连通块大小为 i 的方案数。对于 v 在连通块的时候,有转移:

fu,ij×fv,jfu,i,vsonu,j<i

vu 之间的边不覆盖,则有:

fu,i×fv,jfu,i+j

你乍一看这不就是树上背包吗?时间复杂度 O(n2),可以通过此题!

现在我们已经基本找出状态转移的方程,但现在我们还需要思考一个问题:

一个点数为 k 的连通块,将里面的点不重不漏两两匹配的方案数

首先对于 k 为奇数的时候是无贡献的;所以只用考虑 k 为偶数的情况。考虑递推求解答案,设 sk 表示点数为 k 的贡献。对于一个点,我有 k1 种选择方案,而剩下的 k2 个点的方案是 sk2,固可得递推式:sk=(k1)×sk2

但考虑到我们只是没有考虑这些方案中会有的不合法情况,所以需要稍微容斥一下,在转移的时候还需要给一个 (1)k

然后看到之前的 dp,我们发现对于第一种情况合并两个连通块似乎不好计算方案,于是我们改写状态,设 fu,i 表示以 u 为根的子树,u 所在连通块大小为 i 时不考虑 u 所在连通块中匹配情况的方案数,这样在合并两个连通块时我们就直接把系数乘上就行,所以最后第一种情况的转移式为:

fu,ifu,ij×fv,j×(si)

最后答案就是 if1,i×si

代码

void dfs(int u, int fa){
    sz[u] = f[u][1] = 1;
    for(int i = hd[u]; i; i = e[i].nxt){
        int v = e[i].to; if(v == fa)continue;
        dfs(v, u); copy(f[u], f[u] + 1 + sz[u], g);
        fill(f[u], f[u] + 1 + sz[u], 0);
        for(int j = 1; j <= sz[u]; ++j)for(int k = 1; k <= sz[v]; ++k)
            f[u][j] = del(f[u][j], mul(mul(f[v][k], s[k]), g[j])),
            f[u][j + k] = add(f[u][j + k], mul(g[j], f[v][k]));
        sz[u] += sz[v];
    }
}
posted @   Lyrella  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示