zoukankan      html  css  js  c++  java
  • [NOI2020]超现实树

    超现实树(Surreal)

    时隔 10 个月,我又来复盘此题啦 /se

    当时蒟蒻不会这道题,胡了个三树合并的做法,以为 GG 但发现竟然还有 40 pts /jy

    什么是三棵树合并?一个简单的例子:

    这是第 2 组样例。它的答案为 Almost Complete。对于更复杂的树,如果其它部分完全一样,三棵树分别是如上的结构,那么可以合并到一块,例如

    如果能合并成像右边一样是一个只包含根节点的树,它就是 Almost Complete,否则就是 No

    于是你开始信心满满地开始 rush 一份线性的带 Hash 的代码,当你开始一组组地跑样例时,发现 Case 5 WA 了!

    如果出题人按照开始只下发前 4 组样例,估计大多数错解选手就会跑路看 T1 或 T3,还好他是 Almost 良心 的(bushi),补发了两组!

    此时要反思为什么上面是错的。在第一幅图中:

    ({ m grow}()()+{ m grow}()()+{ m grow}()()={ m grow}()()setminus{)(} e{ m grow}()())

    发现两者不完全等价!我们尝试修正该方法。我们使用红色的节点来 特殊化 被合并的节点,也就是像这样

    按照这样,我们重新合并图二中的树

    但是接下来又要纠结了:带有特殊节点的合并是什么样的?我们不难遐想到有如下多种可能

    合并的组合突然间多了很多。我们试图来讨论。但在讨论前,我们想要弄明白 特殊节点 的含义,就目前来看,它表示:除了特殊节点自身一个节点的树无法生成,其它任意子树皆可被生成

    首先,我们要排除上图中的 (1,4)(最右上角的),这种情况不具有意义。它表明:左右子树同时不能为自身。而在这样的限制条件下,无法产生的树的集合大小为 无限大(令左儿子为自身,右儿子形态任意),即使加强限制使得集合大小有限,该限制 不强于 其它七种情况,换句话说它是 没有用的。想避免此种情况,也就是说红色节点的个数必须 (le 1)

    接着,我们来一一讨论。对于对称的情况我们可以转化,有用的情形总结如下:

    ({ m grow}()()+{ m grow}()()+{ m grow}()()={ m grow}()()setminus{)(~+~)(})

    ({ m grow}()()+{ m grow}()()+{ m grow}()()={ m grow}()()setminus{)(~+~)(~+~)(})

    ({ m grow}()()+{ m grow}()()+{ m grow}()()={ m grow}()()setminus{)(~+~)(~+~)(})

    ({ m grow}()()+{ m grow}()()+{ m grow}()()={ m grow}()()setminus{)(~+~)(~+~)(})

    ({ m grow}()()+{ m grow}()()+{ m grow}()()={ m grow}()()setminus{)(~+~)(~+~)(~+~)(})

    三角形表示该点构成的子树形态任意。

    发现第 2、3、5 种情况下,不可生成的部分中有一种情况,它可以拓展出无数种树。这也直接指出三棵树合并的错误之处了。这时发现第 5 组中:

    ({)(}+{ m grow}()()={ m grow}()()setminus{)(})

    利用右边的红点,可以把右边的子树“封死”,而且不难证明该条件是必要的。这样不可生成的集合大小就是有限的了!

    于是能推导出四棵树合并:

    ({ m grow}()()+{ m grow}()()+{ m grow}()(}+{ m grow}()()={ m grow}()()setminus{)(~+~)(~+~)(~+~)(})

    可是右边的东西还是有点复杂?不过发现:等式右边都满足,由根生长的树,却不能在等式左边生长出来的数目是 有限的。如果拓宽 特殊点 的定义:特殊点所在的子树内对于数量 有限 的拓展下无法生成,除此之外其它基于原树拓展的部分均可生成。

    不难发现上述合并法则依然成立!

    为了统一,我们甚至可以

    这样就可以将四棵树合并的策略适用于任何情况了。注意初始情况下 特殊节点 不可生成的树为空,但只要合并过,就一定非空,可以证明这样转换不会出现超出该合并法则的情况。

    于是修正最开始三棵树合并的代码,使用 Hash 跑四棵树合并,即可通过本题。期望复杂度 (mathcal O(n))

    这道题还要用 Hash?Too weak!这里还有没挖掘的性质,可以大幅降低代码实现难度。

    对于第二幅图中的树,它对答案是不起作用的,因为若干次合并后会出现这样的情况:

    这与前面的推论矛盾了。什么样子的树在合并的过程中不会出现这样的情况?稍加思考可以发现,如果一个儿子合并了,另一个儿子不能合并。该种类树的形态:有一条主链,然后链上的每一个非叶子节点要么仅仅有一个节点,要么没有节点。

    这跟官方题解中 树枝 的定义一致。官方题解中说:对于所有深度为 MAX_DEP 的树枝若均能被生成,则其 Almost Complete。而上面的合并,刚好符合这样的要求。

    于是删掉无用的树之后,我们可以递归合并。这样就大幅简化代码了。复杂度仍然可以做到 (mathcal O(n))

    #include <bits/stdc++.h>
    #define pb push_back
    #define x first
    #define y second
    using std::vector; using std::pair;
    const int N = 2e6 + 5;
    int T, n, m;
    vector<int> lc[N], rc[N];
    vector<pair<int, int>> tmp;
    int dfs(vector<pair<int, int>> A) {
    	vector<pair<int, int>> A0, A1, A2, A3;
    	if (!A.size()) return 0;
    	for (auto u : A) {
    		int x = lc[u.x][u.y], y = rc[u.x][u.y];
    		if (!x && !y) return 1;
    		if (!x || !y) x ? A0.pb({u.x, x}) : A1.pb({u.x, y});
    		else {
    			if (!lc[u.x][y] && !rc[u.x][y]) A2.pb({u.x, x});
    			if (!lc[u.x][x] && !rc[u.x][x]) A3.pb({u.x, y});
    		}
    	}
    	return dfs(A0) && dfs(A1) && dfs(A2) && dfs(A3);
    }
    void solve() {
    	scanf("%d", &n); tmp.clear();
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", &m);
    		lc[i].resize(m + 1), rc[i].resize(m + 1);
    		for (int j = 1; j <= m; j++) scanf("%d%d", &lc[i][j], &rc[i][j]);
    		tmp.pb({i, 1});
    	}
    	puts(dfs(tmp) ? "Almost Complete" : "No");
    }
    int main() {
    	scanf("%d", &T);
    	while (T--) solve();
    	return 0;
    }
    
  • 相关阅读:
    shp2pgsql向postgresql导入shape数据
    node.js的Promise库-bluebird示例
    iOS中点击事件失效的解决办法
    [PHP] 获取IP 和JS获取IP和地址
    [Bootstrap ] 模态框(Modal)插件
    [html][javascript] 关于SVG环形进度条
    [javascript] js实现小数的算术运算方法
    [GO] linux 下安装GO
    小知识点:session的存放位置
    [linux] linux的top命令参数详解
  • 原文地址:https://www.cnblogs.com/ac-evil/p/14897984.html
Copyright © 2011-2022 走看看