BZOJ4013[HNOI2015]实验比较(并查集+树形dp)

题目链接

BZOJ
洛谷

解析

先考虑\(K_i < i\)的情况,由于每个i最多有一个\(K_i\),所以想到按照\(i\)的父亲是\(K_i\)的方式建树
再考虑\(K_i = i\),因为相等的元素交换位置不产生新的贡献,所以显然可以把\(K_i\)\(i\)合并成一个点
注意到答案与儿子之间的顺序无关,然后就可以在树上dp了
\(dp[u][i]\)表示以\(u\)为根的子树,分成\(i\)段(相等的连续元素算一段)的方案数,依次遍历每棵子树,设当前遍历到儿子\(v\),那么

\[dp[u][i] = dp[u][j] * dp[v][k] * T \]

现在的问题在于如何求\(T\)
问题的实质是将两个序列长度分别为\(j\)\(k\)的序列合并为一个长度为\(i\)的序列
转化一下可以理解为有\(j\)个黑球,\(k\)个白球,\(i\)个盒子,每个盒子最多容纳1个白球和1个黑球,所有盒子都不为空的方案数
考虑先放黑球,有\(i \choose j\)种方案,然后考虑白球,必须先放剩下的\(i - j\)个盒子,然后从放黑球的盒子里任选\(k - (i - j)\)个,有\({i \choose j} * {j \choose k - (i - j)}\)种方案,所以

\[T = {i \choose j} * {j \choose k - (i - j)} \]

\[dp[u][i] = dp[u][j] * dp[v][k] * {i \choose j} * {j \choose k - (i - j)} \]

然后。。。就没有然后了
复杂度表面\(O(n^4)\),实际上用\(size\)限制枚举次数可以有效降低

简单总结下

  1. 并查集合并相等的点
  2. 按照\(i\)的父亲是\(K_i\)的方式建树
  3. 树形dp

注意

  1. 看起来转移很简单但是第一个儿子怎么处理我想了好久(是我太菜了吧qaq)
  2. 缩点过后可能是森林,要加一个根节点把它们连起来
  3. 有无解的情况,即通过\(K_i\)连成了个环
  4. 大概就这些了吧。。。。

(丑陋的)代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#define MAXN 110

typedef long long LL;
const LL mod = (LL)1e9 + 7;
struct UF_Set {
	int belong[MAXN];
	void init() { for (int i = 0; i < MAXN; ++i) belong[i] = i; }
	int find(int x) { return belong[x] == x ? x : belong[x] = find(belong[x]); }
	void merge(int x, int y) { x = find(x), y = find(y); if (x ^ y) belong[x] = y; }
} uf;
int N, M;
LL dp[MAXN][MAXN], g[MAXN], C[MAXN][MAXN], ans, noans = 1;
std::vector<int> son[MAXN];
int fa[MAXN], root[MAXN], size[MAXN];

void prepare();
LL qpower(LL, LL);
void DP(int);
int main() {
	std::ios::sync_with_stdio(false);
	std::cin >> N >> M;
	uf.init();
	prepare();
	while (M--) {
		int x, y;
		char s[5];
		std::cin >> x >> s >> y;
		if (s[0] == '=') uf.merge(x, y);
		fa[y] = x;
	}
	for (int i = 1; i <= N; ++i) {
		int x = uf.find(i), y = uf.find(fa[i]);
		if (y && (x ^ y)) son[y].push_back(x), root[x] = 1;
	}
	for (int i = 1; i <= N; ++i)
		if (i == uf.find(i) && !root[i])
			son[0].push_back(i), noans = 0;
	DP(0);
	for (int i = 0; i <= N + 1; ++i)
		(ans += dp[0][i]) %= mod;
	std::cout << (noans ? 0 : ans) << std::endl;
	
	return 0;
}
LL qpower(LL x, LL y) {
	LL res = 1;
	while (y) {
		if (y & 1) (res *= x) %= mod;
		(x *= x) %= mod;
		y >>= 1;
	}
	return res;
}
void prepare() {
	for (int i = 0; i < MAXN; ++i) {
		C[i][0] = 1;
		for (int j = 1; j < MAXN && j <= i; ++j)
			C[i][j] = C[i][j - 1] * (i - j + 1) % mod * qpower(j, mod - 2) % mod;
	}
}
void DP(int u) {
	dp[u][0] = 1;
	size[u] = 1;
	for (int i = 0; i < son[u].size(); ++i) {
		int v = son[u][i];
		DP(v);
		for (int j = 0; j <= size[u]; ++j)
			g[j] = dp[u][j], dp[u][j] = 0;
		for (int j = 0; j <= size[u]; ++j)
			for (int k = 1; k <= size[v]; ++k)
				for (int t = std::max(j, k); t <= std::min(j + k, size[u] + size[v]); ++t)
					(dp[u][t] += g[j] * dp[v][k] % mod * C[t][j] % mod * C[j][k - t + j]) %= mod;
		size[u] += size[v];
	}
	++size[u];
	for (int i = size[u]; i; --i) dp[u][i] = dp[u][i - 1];
	dp[u][0] = 0;
}
//Rhein_E
posted @ 2019-02-20 18:07  Rhein_E  阅读(124)  评论(0编辑  收藏  举报