题目
点这里看题目。
分析
假如已知原图是一个二分图,能不能确定原图的一部?
注意到,在二分图中,如果我们可以给出它的一棵生成树,则即可黑白染色并找出一部。那么,问题就归结到了“如何生成一棵生成树”。
由于生成树是边极少且保证连通的边子集,因此我们可以尽量删边。一旦删了边之后图会不连通,就说明这条边在生成树上。这样我们即可找出一棵生成树。
暴力做的询问次数为 \(\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 分层,则二分图只会存在奇偶性不同的层之间的边,树边也包括在其中。所以,我们可以只保留跨奇偶的边中的树边,再尝试删除每一条树边并检查连通性。可以发现,原图是二分图,当且仅当每次删掉一条树边后,图都不再连通。
小结:
-
本题中对于二分图的处理都值得学习。包括“用生成树定一部”、“删树边验证二分性质”等。
-
在稀疏序列中分块优化查找元素的方法,很适合用在“容易询问某一个集合是否包含至少一个指定元素”的情景之下。
代码
#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 );
}