[ARC101C] Ribbons on Tree

神仙的容斥题与神仙的树形DP题。

首先搞一个指数级的做法:求总的、能够覆盖每一条边的方案数,通过容斥可以得到\(\text{ans}=\sum\limits_E{(-1)^{|E|}F(E)}\)。其中,\(F(E)\)表示钦定删除边集\(E\)后,其他的连边方案数。显然经过删边操作,这张图被划分成了很多联通块,联通块之间没有连边,方案数就是每个联通快的方案数的成绩。特别地,当\(|E|=0\)时,这个是总情况数。

如何求解一个联通块内的连边方案数呢?先上式子:\(\text{ret}=1 \times 3 \times 5 \times \cdots \times (n - 3) \times (n - 1)\)。如何理解这个式子呢?首先这个“联通块”并不是一定要求联通,因为我容斥的时候只要求某个边集断开,并没有要求其他的都联通呀!所以,随便找第一个点,它可以和\(n-1\)个点匹配;然后找下一个点,它可以和\(n-3\)个点匹配······最后总方案树就是它们的乘积。

现在我们有了指数级做法了(就是枚举\(E\),时间复杂度\(\Theta(n^2)\)),现在来尝试把它优化到多项式时间复杂度。

一个“联通块”的情况数,仅仅与“点数”的点数有关,这启示我们在 DP 中存下一维状态来存当前点集的大小;每条边都只有选和不选两种情况,这让我们联想到了 0/1 背包。

\(f_{u,i,0/1}\) 表示,\(u\)这个子树,跟\(u\)结点连着的“联通块”大小是\(i\),此时还未乘上\(i\)这个联通块的情况数的总情况数(也就是把这个子树删到只剩下\(i\)个点,不考虑\(i\)内部情况的总情况数)。显然这时\(f_{u,i,0/1}\)再乘上\(g[i]\)就是总情况数(\(g[i]=1 \times 3 \times \cdots \times (i - 3) \times (n - 1)\))。

转移的时候,考虑怎么合并把\(v\)合并到\(father_v\)上面;我们的目的是优化\(\Theta(n^2)\)枚举边集,所以看这条边钦定删掉和不钦定删掉两种情况,方程分别是:

\[f_{v,j,a} * f_{father,i,b} \rightarrow f^{'}_{father,i+j,a\oplus b} \]

\[f_{v,j,a} * f_{father,i,b} * g[j] \rightarrow f^{'}_{father,i,a\oplus b\oplus 1} \]

上面那个方程的含义是,不钦定删除 <v,fat> 那条边,那么它们下面的总情况数是它们的乘积;下面那个方程的意思是,不选这条边,那么下面的方案数要乘上\(v,b\)的总方案数,即\(f \times g\)

最终的答案就等于\(\sum\limits_{i=1}^n{f_{1,i,0} - f_{1, i, 1}}\)

总结一下,这里有几个重要的点:

1.转化为容斥的问题。

2.结合情况数只与结点数有关和边的出现与否只有两种情况,设计类似于 0/1 背包的 DP 方法。

3.树形 DP 不一定是考虑怎么从子节点们一次性推出父亲,还可以考虑怎么把子节点依次并进父亲的答案里面,这种 DP 的实现,需要把 DP 数组的其中一维拷贝一遍,具体实现看下面:

#include <bits/stdc++.h>
#define LL long long

using namespace std;
const int maxn = 5e3 + 1e2;
const int mod = 1e9 + 7;

int n, f[maxn][maxn][2], g[maxn], sze[maxn];

vector<int> T[maxn];

void dfs(int x, int fa)
{
	int tmp[maxn][2];
	memset(tmp, 0, sizeof tmp);
	//cout << "HH" << endl;
	//cout << x << endl;
	sze[x] = 1;
	f[x][1][0] = 1;
	for (auto s : T[x])
	{
		if (s == fa) continue;
		dfs(s, x);
		for (int i = sze[x]; i; i--)
		{
			for (int j = sze[s]; j; j--)
			{
				for (int a = 0; a < 2; a++)
				{
					for (int b = 0; b < 2; b++)
					{
						(tmp[i + j][a ^ b] += (LL)f[x][i][a] * f[s][j][b] % mod) %= mod;
						(tmp[i][a ^ b ^ 1] += (LL)f[x][i][a] * f[s][j][b] % mod * g[j] % mod) %= mod;
					}
				}
			}
		}
		sze[x] += sze[s];
		for (int i = 0; i <= sze[x]; i++)
		{
			for (int j = 0; j < 2; j++)
			{
				f[x][i][j] = tmp[i][j];
				tmp[i][j] = 0;
			}
		}
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int u, v;
		cin >> u >> v;
		T[u].push_back(v);
		T[v].push_back(u);
	}
	g[0] = 1;
	for (int i = 2; i <= n; i += 2)
	{
		g[i] = (LL)g[i - 2] * (i - 1) % mod;
	}
	dfs(1, 0);
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		(ans += ((LL)f[1][i][0] - f[1][i][1] + mod) * g[i] % mod) %= mod;
	}
	cout << ans << endl;
	
	return 0;
}


posted @ 2020-10-29 00:03  Linshey  阅读(111)  评论(0编辑  收藏  举报