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

转载于:https://www.cnblogs.com/Rhein-E/p/10408239.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值