• 「NOI2020」超现实树


    题目

    点这里看题目。

    分析

    困难的题目。

    思路一

    从命题逻辑的角度考察一棵树的限制。

    某棵树的 \(\operatorname{grow}\) 可以被写作树上结点存在性(在或不在)的合取。考察 \(\operatorname{grow}\) 的并的时候,出于方便运算的考虑可以取补集,于是就变成了析取范式的合取运算

    然而树形结构并没有给析取式带来什么很好的性质,并且两个析取式合并后貌似没法转化成树形,只能作罢。不知道这个思路能不能用在其它题目里面。

    思路二

    即便我们可以给出最后所有树的 \(\operatorname{grow}\) 对应的一个逻辑条件,我们还是得检查“是否有无穷多组解”,倒不如一开始就从这个切口入手。

    考虑 \(m\) 棵树的结点的并集,记为 \(T^\cup\)。可以发现,\(\operatorname{grow}(\mathscr{T})\) 是不完备的,当且仅当存在一棵满足如下条件的树 \(T^*\)

    1. \(T^*\) 包含了 \(T^{\cup}\) 中至少一个叶子。

    2. \(T^*\not\in \operatorname{grow}(\mathscr T)\)

    这个条件的充分性是显然的——我们可以随便替换那一个叶子,从而产生无穷棵 \(\not\in \operatorname{grow}(\mathscr T)\) 的树。而必要性应该可以直接通过分析 \(\not\in \operatorname{grow}(\mathscr T)\) 的树的数量是有限的来达成,就不赘述了。反正我也不会

    那么,如果我们选定了一个叶子 \(l\),怎么检查是否有一棵 \(T^*\) 包含了它且不在 \(\operatorname{grow}(\mathscr T)\) 内?最傻瓜的策略是,我们从树根拉一条路径到 \(l\),但是这当然很容易被一些简单的单链堵住。再做一些调整,我们在其它位置上支出去一些支链——此时就可以发现,支出去的支链必然是叶子,不会更长。如果有另一棵树在支链位置上伸出来了一棵非叶子的子树,那么它不可能防得住我这一个叶子,所以现在我们只需要考虑叶子的限制。

    Note.

    这里的想法仍然是从“傻瓜策略”出发,一路调整。

    思考过程其实更加接近于博弈题目,考虑一些基本的操作,然后看好不好,不好就修修补补,总可以走到一个较优的策略。

    最好要熟悉这样的思考方式,不要下一次又想不起来。

    那么,我们从 \(\mathscr T\) 中的树的角度来分析。如果有 \(T\in \mathscr T\)满足 \(T\) 中有一个叶子 \(\overline l\)\(l\) 或者 \(l\) 的祖先,我们考察 \(T\) 上从根到 \(\overline l\) 的路径:如果路径上有一些不是叶子的支链,这样的 \(T\) 不可能产生任何效果,因为我们只会在支链的位置上放或不放叶子;否则,这条路径就可以被描述成 \(01\)\(S_T\),每个位置描述路径上的结点有没有支出去一个叶子。如果最终我们构造的树上,描述分支情况的 \(01\) 串包含 \(S_T\) 作为一个前缀,那么这样的树就会存在于 \(\operatorname{grow}(T)\) 中,寄了。

    对于某个 \(T^\cup\) 上的叶子 \(l\),找出所有可能的 \(S_T\),构成集合 \(P_l\)。我们现在需要检查是否存在一个 \(01\) 串,使得任意的 \(P_l\) 中的串都不是它的前缀。显然现在我们应该建立 \(P_l\) 的 Trie 树,且为了方便处理,我们将所有儿子个数不充分的非叶子结点的儿子补全。类似于上面的分析,存在一个上述 \(01\) 串的条件就是,存在一个 Trie 上的叶子,满足它的祖先都不在 \(P_l\)。这样的检查可以用标记做到线性。

    Note.

    两个都是在处理特定“存在性”的检查问题。并且还有一定的共性,那就是考虑一些极小的、特殊的不合法解(当然,也可以是极大的、特殊的合法解)。关键在于,我们要主动施加特殊性质,从而避免繁杂的讨论和太多可能性

    从技术的角度来看,我们可以看作是先有“完整的”\(T^\cup\) 和“完整的”Trie(也就是叶子深度相同,完全补全了),然后再收缩大量无用的结点,精简到了只需要考虑有限的结点的情况。

    也可以记忆一下这里的处理方式,毕竟自己还是走了弯路想错过一次的,不要再犯这样的错误了

    现在我们完成了一个叶子的检查。依据树形结构,我们很容易想到按照 DFS 顺序枚举叶子,这样可以充分利用公共信息。所以,我们首先建立出 \(T^\cup\),以及 \(\bigcup_l P_l\) 对应的 Trie 树,并把 Trie“补全”。此时我们将每一个 \(T\) 在叶子上的限制挂到 \(T^{\cup}\) 对应的结点上,在 DFS 的时候我们就在这些结点上将限制插入 Trie。处理 Trie 一边的限制也就容易了,我们将 \(P_l\) 处理成树上标记,这样相当于需要处理区间加减、维护全局最小值,线段树即可胜任。最终复杂度即为 \(O(n\log n)\)

    代码

    #include <cstdio>
    #include <vector>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    const int MAXN = 4e6 + 5;
    
    template<typename _T>
    inline void Read( _T &x ) {
    	x = 0; char s = getchar(); bool f = false;
    	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s ^ '0' ), s = getchar(); }
    	if( f ) x = -x;
    }
    
    template<typename _T>
    inline void Write( _T x ) {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) Write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    inline _T Max( const _T &a, const _T &b ) {
    	return a > b ? a : b;
    }
    
    template<typename _T>
    inline _T Min( const _T &a, const _T &b ) {
    	return a < b ? a : b;
    }
    
    struct Node {
    	int ch[2];
    
    	Node(): ch{} {}
    	Node( int L, int R ): ch{ L, R } {}
    	
    	inline bool IsLeaf() const {
    		return ! ch[0] && ! ch[1];
    	}
    };
    
    typedef std :: vector<Node> Tree;
    
    int mn[MAXN << 2], tag[MAXN << 2];
    int lef[MAXN], rig[MAXN], ID;
    
    std :: vector<int> chg[MAXN];
    Tree tre, pref, uni;
    
    int M;
    
    void DFS1( const int &u, int uUni, int uPref ) {
    	if( tre[u].IsLeaf() ) {
    		if( uPref )
    			chg[uUni].push_back( uPref );
    		return ;
    	}
    	rep( k, 0, 1 ) 
    		if( int v = tre[u].ch[k] ) {
    			if( ! uni[uUni].ch[k] ) {
    				uni.push_back( { 0, 0 } );
    				uni[uUni].ch[k] = ( int ) uni.size() - 1;
    				chg[( int ) uni.size() - 1].clear();
    			}
    			int nxt = uPref, d;
    			if( ! tre[u].ch[k ^ 1] ) d = 0;
    			else {
    				if( ! tre[tre[u].ch[k ^ 1]].IsLeaf() )
    					nxt = 0;
    				else d = 1;
    			}
    			if( ! nxt )
    				DFS1( v, uni[uUni].ch[k], 0 );
    			else {
    				if( ! pref[uPref].ch[d] ) {
    					pref.push_back( { 0, 0 } );
    					pref[uPref].ch[d] = ( int ) pref.size() - 1;
    				}
    				DFS1( v, uni[uUni].ch[k], pref[uPref].ch[d] );
    			}
    		}
    }
    
    void DFS2( const int &u ) {
    	if( pref[u].IsLeaf() ) 
    		lef[u] = rig[u] = ++ ID;
    	else {
    		lef[u] = 1e9, rig[u] = - 1e9;
    		rep( k, 0, 1 )
    			if( int v = pref[u].ch[k] ) {
    				DFS2( v );
    				lef[u] = Min( lef[u], lef[v] );
    				rig[u] = Max( rig[u], rig[v] );
    			}
    	}
    }
    
    inline void Upt( const int &x ) {
    	mn[x] = Min( mn[x << 1], mn[x << 1 | 1] );
    }
    
    inline void Add( const int &x, const int &delt ) {
    	mn[x] += delt, tag[x] += delt;
    }
    
    inline void Normalize( const int &x ) {
    	if( ! tag[x] ) return ;
    	Add( x << 1, tag[x] );
    	Add( x << 1 | 1, tag[x] );
    	tag[x] = 0;
    }
    
    void Build( const int &x, const int &l, const int &r ) {
    	if( l > r ) return ;
    	tag[x] = mn[x] = 0;
    	if( l == r ) return ;
    	int mid = ( l + r ) >> 1;
    	Build( x << 1, l, mid );
    	Build( x << 1 | 1, mid + 1, r );
    }
    
    void Update( const int &x, const int &l, const int &r, const int &segL, const int &segR, const int &delt ) {
    	if( segL <= l && r <= segR ) { Add( x, delt ); return ; }
    	int mid = ( l + r ) >> 1; Normalize( x );
    	if( segL <= mid ) Update( x << 1, l, mid, segL, segR, delt );
    	if( mid  < segR ) Update( x << 1 | 1, mid + 1, r, segL, segR, delt );
    	Upt( x );
    }
    
    inline void Init() {
    	rep( i, 1, ( int ) pref.size() - 1 ) {
    		if( pref[i].IsLeaf() ) continue;
    		if( ! pref[i].ch[0] ) {
    			pref.push_back( Node() );
    			pref[i].ch[0] = ( int ) pref.size() - 1;
    		}
    		if( ! pref[i].ch[1] ) {
    			pref.push_back( Node() );
    			pref[i].ch[1] = ( int ) pref.size() - 1;
    		}
    	}
    	ID = 0, DFS2( 1 );
    	Build( 1, 1, ID );
    }
    
    bool DFS4( const int &u ) {
    	int n = chg[u].size();
    	rep( i, 0, n - 1 ) 
    		Update( 1, 1, ID, lef[chg[u][i]], rig[chg[u][i]], +1 );
    	if( uni[u].IsLeaf() ) {
    		if( mn[1] == 0 )
    			return true;
    	} else 
    		rep( k, 0, 1 )
    			if( int v = uni[u].ch[k] )
    				if( DFS4( v ) ) return true;
    	rep( i, 0, n - 1 )
    		Update( 1, 1, ID, lef[chg[u][i]], rig[chg[u][i]], -1 );
    	return false;
    }
    
    int main() {
    	int T; Read( T );
    	while( T -- ) {
    		Read( M );
    		pref.clear();
    		pref.push_back( { 0, 0 } );
    		pref.push_back( { 0, 0 } );
    		uni.clear();
    		uni.push_back( { 0, 0 } );
    		uni.push_back( { 0, 0 } );
    		chg[0].clear();
    		chg[1].clear();
    		bool anyOne = false;
    		rep( i, 1, M ) {
    			int n; Read( n );
    			tre.clear();
    			tre.push_back( { 0, 0 } );
    			rep( j, 1, n ) {
    				int x, y;
    				Read( x ), Read( y );
    				tre.push_back( { x, y } );
    			}
    			anyOne |= n == 1;
    			DFS1( 1, 1, 1 );
    		}
    		if( anyOne ) {
    			puts( "Almost Complete" );
    			continue;
    		}
    		Init();
    		puts( ! DFS4( 1 ) ? "Almost Complete" : "No" );
    	}
    	return 0;
    }
    
  • 相关阅读:
    Microsoft office 2013安装图解
    6.2单一继承
    #include <QLabel>
    #include <QDebug>
    9.1运算符重载
    简单QT界面信号图形化输入输出
    类指针引用
    NULL和nullptr的区别
    网易云课堂_C语言程序设计进阶_第8周:图形交互程序
    5.3友元函数
  • 原文地址:https://www.cnblogs.com/crashed/p/16544993.html
Copyright © 2020-2023  润新知