• 「UOJ751」神隐


    题目

    点这里看题目。

    分析

    交互题好难啊.jpg

    我们先来分析一下怎么才能找出来一条边。假如编号为 \(k\) 的边被加入到 \(G\) 中的询问集合为 \(Q_k\),则询问必须满足对于任意的存在公共点的 \(e_1,e_2\)\(Q_{e_1},Q_{e_2}\) 之间不存在包含关系。否则我们无法准确地确定边的端点。

    但是,我们并不知道哪些边存在公共点,所以对于任意两条边,它们的 \(Q\) 都必须不同

    这个构造还是比较经典的。第一种方法是二进制分组,需要 \(2\lceil\log_2(n-1)\rceil\) 次询问;第二种方法是重标号(所有的标号所含的 \(1\) 的位数相同)之后二进制分组,需要的询问次数略大于 \(\log_2(n-1)\)

    Remark.

    如果分析不出来这里的性质,该怎么想到二进制分组?

    数据范围明示 \(\log n\) 次询问,能够达到这个询问次数的策略不多,其实就只有:二分搜索、分治、随机化折半、二进制分组等等。全部试一遍,只要不搞忘就不怕想不到 。

    有了询问的构造之后(第二种构造方法),如何推理出边?暴力的方法是,枚举所有边 \((u,v)\)。假如新的标号总位数为偶数 \(m\),则 \((u,v)\) 在同一条边中当且仅当在恰好 \(\frac m 2\) 次询问中 \(u,v\) 同属一个连通块。这样复杂度是 \(O(n^2\log n)\) 的。

    怎么加速这个过程?我们选择树上的一些特殊的结点入手,在这个“删边连通性”的条件下,我们选择讨论叶子。

    如果一个点是叶子,则它必然在恰好 \(\frac m 2\) 次询问中是孤立点。这样我们可以逐个剥叶子直到树变为空。反过来,我们逐个加入叶子,并确定叶子连出的边的另一个端点。如果我们确定最后被删的结点为根,与叶子相邻的结点为 \(w\),则必然存在一个连通块,其包含了 \(w\) 却不包含 \(w\) 的父亲。在这样一个连通块中,\(w\) 作为最浅的结点存在。因此,我们取这 \(\frac m 2\) 个连通块的最浅的结点中最深的那一个,就找到了 \(w\)

    最终可以实现一个 \(O(n\log n)\) 的算法。

    代码

    #include <vector>
    #include <algorithm>
    #include "tree.h"
    
    namespace Studio {
    	#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 = 2e5 + 5, MAXLOG = 25;
    
    	typedef std :: pair<int, int> Edge;
    	typedef std :: vector<Edge> RetType;
    	typedef std :: vector<int> Component;
    	typedef std :: vector<Component> QueryType;
    
    	int C[21][21];
    
    	int leaf[MAXN], h, t;
    
    	std :: vector<int> qry;
    	QueryType cpnt[MAXLOG];
    	RetType ans;
    
    	int seq[MAXN], tot;
    	int dep[MAXN];
    
    	int hgst[MAXLOG][MAXN];
    	int rem[MAXLOG][MAXN];
    	int col[MAXLOG][MAXN];
    	bool tkn[MAXN];
    
    	int n, m;
    	
    	inline bool ChkLeaf( const int &u ) {
    		int sgl = 0;
    		rep( i, 0, m - 1 )
    			sgl += rem[i][col[i][u]] == 1;
    		return sgl == ( m >> 1 );
    	}
    
    	inline void Remove( const int &u ) {
    		rep( i, 0, m - 1 )
    			if( -- rem[i][col[i][u]] == 1 ) {
    				int c = col[i][u];
    				for( const int &x : cpnt[i][c] )
    					if( ! tkn[x] ) {
    						if( ChkLeaf( x ) )
    							leaf[++ t] = x;
    						break;
    					}
    			}
    	}
    
    	inline void Insert( const int &u ) {
    		rep( i, 0, m - 1 ) {
    			int c = col[i][u];
    			if( hgst[i][c] == -1 || dep[hgst[i][c]] > dep[u] )
    				hgst[i][c] = u;
    			rem[i][c] ++;
    		}
    	}
    
    	inline int FindFath( const int &u ) {
    		int f = -1, c;
    		rep( i, 0, m - 1 )
    			if( rem[i][c = col[i][u]] )
    				if( f == -1 || dep[f] < dep[hgst[i][c]] )
    					f = hgst[i][c];
    		return f;
    	}
    
    	RetType Solve( const int &N ) {
    		// n = N, m = n <= 2000 ? 14 : 20;
    		n = N;
    		rep( i, 0, 20 ) {
    			C[i][0] = C[i][i] = 1;
    			rep( j, 1, i - 1 )
    				C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
    		}
    		rep( i, 0, 20 )
    			if( ! ( i & 1 ) && C[i][i >> 1] >= n - 1 ) {
    				m = i; break;
    			}
    		for( int S = 0 ; S < ( 1 << m ) ; S ++ )
    			if( __builtin_popcount( S ) == m / 2 )
    				seq[tot ++] = S;
    		qry.resize( n - 1 );
    		rep( i, 0, m - 1 ) {
    			rep( j, 0, n - 2 )
    				qry[j] = seq[j] >> i & 1;
    			cpnt[i] = query( qry );
    			int c = cpnt[i].size();
    			rep( j, 0, c - 1 ) {
    				hgst[i][j] = -1;
    				rem[i][j] = cpnt[i][j].size();
    				for( const int &x : cpnt[i][j] )
    					col[i][x] = j;
    			}
    		}
    		h = 1, t = 0;
    		rep( u, 0, n - 1 )
    			if( ChkLeaf( u ) )
    				leaf[++ t] = u;
    		while( h <= t ) {
    			int u = leaf[h ++];
    			tkn[u] = true, Remove( u );
    		}
    		dep[leaf[t]] = 0;
    		Insert( leaf[t] );
    		per( i, t - 1, 1 ) {
    			int u = leaf[i],
    				f = FindFath( u );
    			ans.push_back( { u, f } );
    			dep[u] = dep[f] + 1, Insert( u );
    		}
    		return ans;
    	}
    }
    
    std :: vector<std :: pair<int, int> > solve( int n ) {
    	return Studio :: Solve( n );
    }
    
  • 相关阅读:
    Java核心类库——线程Thread
    xml基本写法和dtd schema的用法,JAVA读写XML
    Java核心类库——文件和文件夹的管理File类
    使用文件依赖项缓存页输出
    根据 HTTP 标头缓存页的版本
    缓存 ASP.NET 页的某些部分
    根据请求浏览器缓存页的版本
    根据自定义字符串缓存页的版本
    缓存页的多个版本
    阿拉的宣告~~~
  • 原文地址:https://www.cnblogs.com/crashed/p/16572732.html
Copyright © 2020-2023  润新知