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

    题目链接

    实际上这题也不是真不可做,毕竟考场上有二十多个人切了这题

    题意简述

    对于非空、有根、区分左右孩子的二叉树,叶子指无儿子的点;单步替换指将一个叶子替换成一个任意的二叉树;替换指进行一些单步替换;一些二叉树的生长集指这些二叉树经过替换能形成的二叉树集合的并;一个生长集是几乎完备的,当且仅当只有有限棵二叉树不在生长集里面。

    现给定一些二叉树,求这些二叉树的生长集是否是几乎完备的。多组数据。(sum n le 2 imes 10^6)

    题解

    4pts

    不管什么全输出 Almost Complete 即可。

    原因:第一组数据只有一个点,能生成所有树。

    12pts

    (我的实际得分)

    对前三个点进行特判。

    16pts

    定义一棵二叉树“被覆盖”,当且仅当在给定的树集中,存在一棵树能够生长出这棵二叉树。

    暴力搞出所有深度不超过 (maxh) 的二叉树,一一判断其是否被覆盖。复杂度 (O(2^{2^H})),据说实际能跑过五个点。(并不会实现)

    32pts

    “替换”是具有传递性的。即,如果 A 能覆盖 B,B 能覆盖 C,那么 A 能覆盖 C。(显然)

    定义一棵二叉树是“树枝”当且仅当这颗二叉树不存在任何节点,其左右子树的大小都大于1.换言之,“树枝”能被看作一个贯穿上下的“主干”链身上挂着一些叶子。

    显然,非树枝的二叉树一定能被树枝覆盖,非树枝不能被覆盖,树枝也不能被覆盖;非树枝可以被覆盖,树枝还不一定能被覆盖。换言之,树枝的 Hack 能力更强。

    因此我们只需要枚举所有树枝的情况,看看是否有不能被覆盖的树枝即可。复杂度 (O(2^{2H}))(并不会实现)

    36pts

    对于一堆链的情况,特判是否有没有一个节点的树即可。因为除了一个节点的树,其余都不能覆盖“人”字形的二叉树。

    100pts

    给定的树中,非树枝的二叉树没用。因为构造一个链即可 Hack 掉它,并且如果不想被 Hack,那么就需要一些能够覆盖非树枝或基本能覆盖非树枝(只留有限个 Hack)的树枝。

    这样,我们只需要知道剩下的这些树枝能不能覆盖所有树枝(只留下有限的树枝)。

    树枝树枝树枝,哪里都是树枝,我们需要一种更好的表示树枝的方法。

    一个树枝能被两个01串唯一确定。A串表示树枝主干上每条边的走向(向左0还是向右1),B串表示树枝的主干的某一节的旁边有(1)没有(0)叶子

    显然,一个树枝可以用两棵 01Trie 上同一层的两个点表示。这样,我们就可以在 01Trie 上考虑这些问题了。

    重新定义覆盖:树枝 A 覆盖 B,当且仅当 Trie 上 A 的 a 串是 B 的 a 串的祖先,A 的 b 串也是 B 的 b 串的祖先

    只有有限的 Hack 当且仅当对于所有未被覆盖的点对,其子树两两之间的路径都被覆盖了,使得向下的生长是有限的。存在无限的 Hack 当且仅当存在未被覆盖的点对,其向下的转移方式并没有被覆盖全。

    由上面那条结论可知,我们只需要考虑 Trie 上的所有叶节点,不需要考虑树干的节点。不过我们需要补上那些缺一个叶子的主干的那个叶子。

    生长集“几乎完备”,当且仅当任意叶节点点对都能被覆盖。

    注意是任意叶节点,不一定是同层的叶节点。因为可能会存在我们“忽略”的叶节点和同层叶节点配对没有被覆盖,而如果发生这种情况,被忽略的叶节点的上面的那个真叶节点一定没有被覆盖。相反,不难证明如果存在任意叶节点点对没被覆盖,那么存在无限种 Hack .

    于是我们得到了一种算法:在第一棵 Trie 上 DFS,维护向上的链在第二棵 Trie 上的覆盖情况。如果在 DFS 第一棵 Trie 的某个叶子的时候发现第二棵 Trie 上有叶子没有被覆盖,那么就不是“几乎完备”,否则是“几乎完备”的。

    于是问题转化为区间加全局查最小。线段树维护即可。注意 dfn 可能需要一些小 trick。

    关键代码:

    int trie[2][N][2], tot[2];
    inline int Get(int t, int p, int d) {
    	if (!trie[t][p][d])	trie[t][p][d] = ++tot[t];
    	return trie[t][p][d];
    }
    bool flag;
    int siz[N];
    int dfs_che(int cur) {
    	if (!cur)	return 0;
    	siz[cur] = 1;
    	int lsz = dfs_che(son[cur][0]), rsz = dfs_che(son[cur][1]);
    	if (lsz > 1 && rsz > 1)	return flag = true, 0;
    	return siz[cur] = siz[cur] + lsz + rsz;
    }
    vector<int> lk[N];
    void dfs(int cur, int np0, int np1) {
    	if (siz[cur] == 1) {
    		lk[np0].push_back(np1);
    		return ;
    	}
    	if (siz[son[cur][0]] > 1) {
    		np1 = Get(1, np1, son[cur][1] ? 1 : 0);
    		np0 = Get(0, np0, 0);//Attention!!!
    		dfs(son[cur][0], np0, np1);
    		return ;
    	}
    	if (siz[son[cur][1]] > 1) {
    		np1 = Get(1, np1, son[cur][0] ? 1 : 0);
    		np0 = Get(0, np0, 1);//Attention!!!
    		dfs(son[cur][1], np0, np1);
    		return ;
    	}
    	if (siz[son[cur][0]] == 1 && siz[son[cur][1]] == 1) {
    		dfs(son[cur][0], Get(0, np0, 0), Get(1, np1, 1));
    		dfs(son[cur][1], Get(0, np0, 1), Get(1, np1, 1));
    		return ;
    	}
    	if (siz[son[cur][0]] == 1) {
    		dfs(son[cur][0], Get(0, np0, 0), Get(1, np1, 0));
    		return ;
    	}
    	dfs(son[cur][1], Get(0, np0, 1), Get(1, np1, 0));
    }
    int dfn[N], dcnt, st[N], ed[N];
    bool isleave(int t, int cur) { return !trie[t][cur][0] && !trie[t][cur][1]; }
    void dfs_dfn(int cur) {
    	if (isleave(1, cur))	st[cur] = ed[cur] = dfn[cur] = ++dcnt;
    	else {
    		st[cur] = dcnt + 1;
    		dfs_dfn(trie[1][cur][0]);
    		dfs_dfn(trie[1][cur][1]);
    		ed[cur] = dcnt;
    	}
    }
    
    int ls[NN], rs[NN], mn[NN], tag[NN], ttot, root;
    inline void pushup(int cur)
    inline void pushtag(int cur, int v)
    inline void pushdown(int cur)
    void build(int L, int R, int &cur)
    void add(int L, int R, int l, int r, int v, int cur)
    void dfs_tre(int cur) {
    	if (flag)	return ;
    	for (register uint i = 0; i < lk[cur].size(); ++i)
    		add(1, dcnt, st[lk[cur][i]], ed[lk[cur][i]], 1, root);//Attention!!!
    	if (isleave(0, cur)) {
    		if (!mn[root])	return flag = true, void();
    	} else {
    		dfs_tre(trie[0][cur][0]);
    		dfs_tre(trie[0][cur][1]);
    	}
    	for (register uint i = 0; i < lk[cur].size(); ++i)
    		add(1, dcnt, st[lk[cur][i]], ed[lk[cur][i]], -1, root);
    }
    
    inline void work() {
    	tot[0] = tot[1] = 1;
    	int m; read(m);
    	for (register int i = 1; i <= m; ++i) {
    		int n; read(n);
    		for (register int j = 1; j <= n; ++j) read(son[j][0]), read(son[j][1]);
    		flag = false;
    		dfs_che(1);
    		if (!flag) dfs(1, 1, 1);
    		for (register int j = 1; j <= n; ++j)	siz[j] = 0;
    	}
    	for (register int t = 0; t < 2; ++t) {
    		int up = tot[t];
    		for (register int p = 1; p <= up; ++p)
    			for (register int d = 0; d < 2; ++d)
    				if (!trie[t][p][d] && trie[t][p][d ^ 1])	trie[t][p][d] = ++tot[t];
    	}
    	dfs_dfn(1);
    	build(1, dcnt, root);
    	flag = false;
    	dfs_tre(1);
    	if (!flag)	puts("Almost Complete");
    	else	puts("No");
    }
    
  • 相关阅读:
    linux三剑客之sed
    线程与循环的区别?
    Notify和NotifyAll的区别?
    no system images installed for this target这个问题如何解决?
    Intent里ACTION的CALL和DIAL的区别?
    onConfigurationChanged方法的使用
    String和StringBuffer的区别?
    Activity的状态保存
    C#将datatable数据转换成JSON数据的方法
    SQL语句:关于复制表结构和内容到另一张表中的SQL语句
  • 原文地址:https://www.cnblogs.com/JiaZP/p/13535610.html
Copyright © 2011-2022 走看看