• 「UOJ461」新年的 dog 划分


    题目

    点这里看题目。

    分析

    假如已知原图是一个二分图,能不能确定原图的一部?

    注意到,在二分图中,如果我们可以给出它的一棵生成树,则即可黑白染色并找出一部。那么,问题就归结到了“如何生成一棵生成树”。

    由于生成树是边极少且保证连通的边子集,因此我们可以尽量删边。一旦删了边之后图会不连通,就说明这条边在生成树上。这样我们即可找出一棵生成树。

    暴力做的询问次数为 \(\binom{n}{2}\)。找下一条树边的过程显然可以二分(将所有可能的边拉出来排成一个序列),于是优化到了 \((n-1)\log_2\binom{n}{2}< 2n\log_2n\),还是不够优秀。

    题解给出的做法为 BFS 过程中执行二分过程,从而去掉了 2 的常数,不过说实话没看懂。尝试对于这个序列进行分块,块长为 \(T\)由于 \(n-1\) 条边分布在 \(\binom{n}{2}\) 条边中非常的稀疏,则我们可以预先检查某一块内部有没有树边,有的话再进行二分,询问次数降到了 \(\frac{\binom{n}{2}}{T}+(n-1)\log_2T<\frac{n^2}{T}+n\log_2T\)

    假设 \(T=n^x\),则相当于求 \(\min_{x\in[0,2]}n^{2-x}+xn\log_2n\)。算一下大概可以得到在 \(x=1+\frac{\ln\ln 2}{\ln n}\) 时取到最小值。

    接下来,怎么检查是不是二分图?即便不是二分图,我们也可以得到一棵生成树。对于整个图进行 BFS 分层,则二分图只会存在奇偶性不同的层之间的边,树边也包括在其中。所以,我们可以只保留跨奇偶的边中的树边,再尝试删除每一条树边并检查连通性。可以发现,原图是二分图,当且仅当每次删掉一条树边后,图都不再连通

    小结:

    1. 本题中对于二分图的处理都值得学习。包括“用生成树定一部”、“删树边验证二分性质”等。

    2. 在稀疏序列中分块优化查找元素的方法,很适合用在“容易询问某一个集合是否包含至少一个指定元素”的情景之下。

    代码

    #include "graph.h"
    #include <vector>
    
    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 = 205;
    
    	bool onTre[MAXN][MAXN];
    
    	int dep[MAXN], q[MAXN];
    
    	std :: vector<int> ans;
    	std :: vector<int> neigh[MAXN];
    
    	std :: vector<std :: pair<int, int> > tmp, alr;
    	
    	void AddE( const int &from, const int &to ) {
    		neigh[from].push_back( to );
    		neigh[to].push_back( from );
    	}
    
    	std :: vector<int> Work( const int &n ) {
    		rep( i, 0, n - 2 ) {
    			int lim = i + 1;
    			while( lim <= n - 1 ) {
    				tmp = alr;
    				rep( j, lim, n - 1 )
    					tmp.push_back( { i, j } );
    				if( query( tmp ) ) break;
    				int l = lim, r = n - 1, mid;
    				while( l < r ) {
    					tmp = alr;
    					mid = ( l + r ) >> 1;
    					rep( j, lim, mid )
    						tmp.push_back( { i, j } );
    					if( query( tmp ) ) l = mid + 1;
    					else r = mid;
    				}
    				AddE( i, l );
    				rep( j, lim, l - 1 )
    					alr.push_back( { i, j } );
    				lim = l + 1;
    			}
    		}
    		rep( i, 0, n - 1 ) dep[i] = -1;
    		int h = 1, t = 0;
    		dep[q[++ t] = 0] = 0;
    		while( h <= t ) {
    			int u = q[h ++];
    			for( const int &v : neigh[u] )
    				if( dep[v] == -1 ) dep[q[++ t] = v] = dep[u] + 1;
    		}
    		rep( u, 0, n - 1 )
    			for( const int &v : neigh[u] )
    				onTre[u][v] = onTre[v][u] = true;
    		alr.clear();
    		rep( i, 0, n - 1 )
    			rep( j, i + 1, n - 1 )
    				if( ( dep[i] & 1 ) != ( dep[j] & 1 )
    				    && ! onTre[i][j] ) alr.push_back( { i, j } );
    		rep( i, 0, n - 1 )
    			rep( j, i + 1, n - 1 )
    				if( onTre[i][j] ) {
    					tmp = alr, tmp.push_back( { i, j } );
    					if( query( tmp ) ) return std :: vector<int> ();
    				}
    		ans.clear();
    		rep( i, 0, n - 1 )
    			if( dep[i] & 1 )
    				ans.push_back( i );
    		return ans;
    	}
    }
    
    std :: vector<int> check_bipartite( int n ) {
    	return Studio :: Work( n );
    }
    
  • 相关阅读:
    布局-float-margin-padding
    乡镇投票笔记
    Ajax实战
    Form,tagName和nodeName的区别
    Form,选择并转移导航菜单
    如何在ajax请求中设置特殊的RequestHeader
    nginx配置转发详解
    利用循环遍历的方式判断某个对象是否属于这个数组
    几个原生js方法总结
    chrome JS关闭当前页无效问题
  • 原文地址:https://www.cnblogs.com/crashed/p/16300597.html
Copyright © 2020-2023  润新知