torus711 のアレ

主に競技プログラミングの問題について書きます.PC 以外だと数式が表示されないかもしれないです

AtCoder Beginner Contest 177, D : Friends

問題概要

 $N$ 人の人がいて,「人 $A_i$ と人 $B_i$ が友達である」という情報が $M $ 個与えられる.ここで,友達関係は transitive である(その情報が与えられるとは限らない*1).
 人々をいくつかのグループに分け,任意の人について「同じグループに友人がいない」状態を作りたい.最小でいくつのグループを作る必要があるか?

制約

  • $2 \leq N, M \leq 2 \times 10^5$

解法

 人を頂点,友達関係を辺としたグラフを考えてみると,友達関係の transitivity から,このグラフはクリークをいくつか集めてきたグラフ(それぞれの連結成分がクリークという意味)になっています.鳩ノ巣原理的に考えると,最大のクリークサイズ未満のグループ数では,友達同士で同じグループに入ってしまいます.一方,最大クリークサイズと同じだけのグループ数があれば,友達同士をバラけさせてグループ分けできます.
 ということで,最大クリークサイズが欲しい気になってきますが,今回は一般のグラフではなくかなり制限された形のグラフなので*2,比較的簡単に解くことができます.特別な道具を使わないのであれば,DFS などで連結成分毎に頂点数を数える手法が有効です.素集合データ構造*3があれば,unite 時の操作にちょっと書き足すだけで,グループ毎にサイズを求めることができます.

コード

class DisjointSetForest;
bool DisjointSetForest::unite( int x, int y )
{
	x = find( x );
	y = find( y );

	if ( x == y )
	{
		return false;
	}

	if ( rank[x] < rank[y] )
	{
		parent[x] = y;
		sizes[y] += sizes[x];
	}
	else
	{
		parent[y] = x;
		sizes[x] += sizes[y];
		if ( rank[x] == rank[y] )
		{
			++rank[x];
		}
	}
	--groups_;
	return true;
}
// DisjointSetForest( N )
// find( x )
// same( x, y )
// unite( x, y )
// groups()
// groupSize( x )

int main()
{
	IN( int, N, M );

	DisjointSetForest dsf( N );
	REP( M )
	{
		IN( int, u, v );
		--u, --v;
		dsf.unite( u, v );
	}

	int res = 0;
	REP( i, N )
	{
		chmax( res, dsf.groupSize( i ) );
	}
	cout << res << endl;

	return 0;
}

*1:サンプル見ないと曖昧じゃない?

*2:一般のグラフだと「むずかしい」問題です

*3:いわゆる Union-Find