Codeforces Round #309 (Div. 1) C. Love Triangles
これも考察が結構深くて面白かったです。
解法
uwiさんのつぶやきを参考にして考えました(途中から考え方がずれてきてる気がしますが)。
Cは最終的にクリーク2個以下にならなきゃいけない。とりあえず1をくっつけて、中に0がいたら死、外側の0でグラフをつくって2部グラフでなければ死。それ以外なら2^(0の連結成分の個数-1)が答えだとおもった
— 有為 (@uwitenpen) 2015, 6月 24
とりあえず「最終的にクリーク2個以下にならなきゃいけない」というところを考えてみましょう。
グラフ理論において,グラフGの部分グラフCがクリークとは,C上のすべての頂点が,自分以外のCの頂点全てと連結していることを言います。完全グラフの部分グラフバージョンみたいな感じですね。
また,この問題において,頂点v, uが連結している必要十分条件は,vとuがloveの関係にあることを言います。
これを元に「最終的にクリーク2個以下にならなきゃいけない」をイメージ化すると以下のとおりです。確かにそうならないとダメっぽいですね(証明も図を見れば簡単に示せそうな気がする)。
ということで,loveの関係にある頂点同士をunionFindなどで結びつけると,それぞれのグループはクリークでなければならないことがわかります(これは最後のNGの例を見ればわかると思います)。同じグループでa[i], b[i]についてc[i] == 0となるものがあったら答えは0です。
unionFindでグループが出来たとします。クリークは最大で2つまでに圧縮しないといけないので,今度はそれぞれのグループを合併することを考えましょう。
やはり写真最後のNGの例により,グループ同士でhateの関係が一つでもある物同士を同じクリークとして合併することは出来ません。とりあえずこのhateの関係が一つでもあるグループ同士を辺で結んだ新しいグラフを考えます。
このように合併してはいけないクリーク同士の情報を集めていくと,もしかしたらクリークの数が2より大きくならざるを得ない状態になるかもしれません(例えばa, b, cというグループがあって,aとbは合併できない,bとcは合併できない,cとaは合併できない,という状態になると,結局a, b, cという3つのクリークが必要になるが,これは条件に合わない)。
こんな感じで答えが0になる可能性はどのように判定できるかというと,この「グループ同士の関係を表したグラフ」が二部グラフになっているかどうかで判定することが出来ます。
@y_mazun BFSで2色で塗っていけばOK
— 有為 (@uwitenpen) 2015, 6月 24
以上の可能性を考慮してすべて大丈夫だった場合,「どのようにクリーク同士をつなげるか」というのが答えです。
これには再びunionFindが活躍します。辺で結ばれたグループ同士をさらに大きな同じグループとみなして2^(グループの数-1)を求めれば良いです。
struct UnionFind { vector<int> par; int n, cnt; UnionFind(const int& x = 0) {init(x);} void init(const int& x) {par.assign(cnt=n=x, -1);} inline int find(const int& x) {return par[x] < 0 ? x : par[x] = find(par[x]);} inline bool same(const int& x, const int& y) {return find(x) == find(y);} inline bool unite(int x, int y) { if ((x = find(x)) == (y = find(y))) return false; --cnt; if (par[x] > par[y]) swap(x, y); par[x] += par[y]; par[y] = x; return true; } inline int count() const {return cnt;} inline int count(int x) {return -par[find(x)];} }; const int MAXN = 100100; const ll MOD = 1e9+7; int n, m; int a[MAXN], b[MAXN], c[MAXN]; UnionFind uf; vector<int> G[MAXN]; int color[MAXN]; bool dfs(int cur, int c) { color[cur] = c; for (int v : G[cur]) { if (color[v] == c) return false; if (color[v] == -c) continue; if (!dfs(v, -c)) return false; } return true; } int main() { cin.tie(0); ios::sync_with_stdio(false); cin >> n >> m; uf.init(n+10); for (int i = 0; i < m; i++) { cin >> a[i] >> b[i] >> c[i]; if (c[i] == 1) { uf.unite(a[i], b[i]); } } for (int i = 0; i < m; i++) { if (c[i] == 0) { if (uf.same(a[i], b[i])) { cout << 0 << endl; return 0; } } } map<int, int> M; int k = 0; for (int i = 1; i <= n; i++) { if (M.find(uf.find(i)) == M.end()) M.insert(make_pair(uf.find(i), k++)); } for (int i = 0; i < m; i++) { if (c[i] == 0) { G[M[uf.find(a[i])]].push_back(M[uf.find(b[i])]); G[M[uf.find(b[i])]].push_back(M[uf.find(a[i])]); } } for (int i = 0; i < k; i++) { sort(G[i].begin(), G[i].end()); G[i].erase(unique(G[i].begin(), G[i].end()), G[i].end()); } for (int i = 0; i < k; i++) { if (color[i] == 0) if (!dfs(i, 1)) { cout << 0 << endl; return 0; } } UnionFind uu(k); for (int i = 0; i < m; i++) { if (c[i] == 0) uu.unite(M[uf.find(a[i])], M[uf.find(b[i])]); } ll ans = 1; for (int i = 0; i < uu.count()-1; i++) (ans *= 2) %= MOD; cout << ans << endl; return 0; }