BZOJ4013[HNOI2015]实验比较(并查集+树形dp)
题目链接
解析
先考虑\(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\)限制枚举次数可以有效降低
简单总结下
- 并查集合并相等的点
- 按照\(i\)的父亲是\(K_i\)的方式建树
- 树形dp
注意
- 看起来转移很简单但是第一个儿子怎么处理我想了好久(是我太菜了吧qaq)
- 缩点过后可能是森林,要加一个根节点把它们连起来
- 有无解的情况,即通过\(K_i\)连成了个环
- 大概就这些了吧。。。。
(丑陋的)代码
#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