AtCoder Grand Contest 011: C - Squared Graph
Dを優先してほぼ手を付けなかったが、解けていてもよい問題だった。
solution
連結成分への分解と奇閉路の検出をして、その数を元に計算。O(N+M)。
元のグラフ上で互いに到達不能な頂点同士は、積グラフ上でも互いに到達不能な頂点群を生む。 よって、元のグラフを連結成分に分解してその対ごとに考えてよい。 次のように問題を読み替えられる: 連結グラフGi (0≤i<k)が与えられるので、各(i,j)について積グラフGi×Gjの頂点数を数え、その総和を答えよ。
連結グラフG,Hをとる。 G,H,G×Hの頂点数をそれぞれx,y,zとする。 x=1またはy=1である場合は辺が1本も生まれないので z=xy。 それ以外の場合では、G,Hは共に連結なのでz∈{1,2}になる。 x=y=2の場合の積グラフが縦横に並ぶものと考えればそうなる。 ここでz=1となるのはG,Hのどちらかが奇閉路を持つとき。 つまり、それぞれのグラフを以下のように分類すれば十分:
- |V|=1
- |V|≥2 かつ 奇閉路がない
- |V|≥2 かつ 奇閉路がある
奇閉路の存在は二部グラフ性と同値なので、連結成分への分解の際にまとめてやるとよい。
implementation
#include <iostream>
#include <vector>
#include <queue>
#define repeat(i,n) for (int i = 0; (i) < int(n); ++(i))
using ll = long long;
using namespace std;
int main() {
// input
int n, m; cin >> n >> m;
vector<vector<int> > g(n);
repeat (i,m) {
int u, v; cin >> u >> v; -- u; -- v;
g[u].push_back(v);
g[v].push_back(u);
}
// decompose
int a_components = 0, a_nodes = 0; // connected graph: |V| = 1
int b_components = 0, b_nodes = 0; // connected graph: |V| >= 2 and no odd cyles
int c_components = 0, c_nodes = 0; // connected graph: |V| >= 2 and odd cycles exist
vector<char> used(n);
repeat (root,n) if (not used[root]) {
bool is_bipartite = true;
int size = 0;
queue<int> que;
que.push(root);
used[root] = 'A';
while (not que.empty()) {
int i = que.front(); que.pop();
++ size;
for (int j : g[i]) {
if (used[j]) {
if (used[i] == used[j]) is_bipartite = false;
} else {
used[j] = (used[i] == 'A' ? 'B' : 'A');
que.push(j);
}
}
}
if (size == 1) {
++ a_components; a_nodes += size;
} else if (is_bipartite) {
++ b_components; b_nodes += size;
} else {
++ c_components; c_nodes += size;
}
}
// calculate
ll ans = 0;
ans += a_nodes *(ll) a_nodes; // A A
ans += 2 * a_nodes *(ll) b_nodes; // A B ; B A
ans += 2 * a_nodes *(ll) c_nodes; // A C ; C A
ans += b_components *(ll) b_components * 2; // B B
ans += 2 * b_components *(ll) c_components; // B C ; C B
ans += c_components *(ll) c_components; // C C
// output
cout << ans << endl;
return 0;
}