• 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
    
  • 相关阅读:
    wpf-x-指令元素
    意法半导体STM32单片机特性
    非易失性存储器MRAM的两大优点
    静态SDRAM和动态SDRAM的区别
    使用SRAM如何节省芯片面积
    不同类别存储器基本原理
    串口SRAM和并口SRAM的引脚区别
    SRAM存储器芯片地址引脚线短路检测方法
    2020年国内MCU市场有望突破500亿元
    MRAM可以替代NOR或SRAM
  • 原文地址:https://www.cnblogs.com/Rhein-E/p/10408239.html
Copyright © 2020-2023  润新知