AOJ 1388 - Counting Cycles
解法
m <= n + 15 なので、サイクル基底を考えれば高々 2^16 通りしかないことがわかる。
つまり愚直にサイクルを探索しても、そこまで探索空間は大きくない。
サイクル空間を考えるにしろ、n, m が大きすぎるので、小さくしたい。
よく考えると、次数が 2 以下の頂点は縮約なり削除しても結果に影響しないことがわかる。
これによって、頂点数(辺の数)は k = m - n + 1 として 2k 程度に落ちることがわかる。
ここまで小さくなれば、愚直に探索しても十分間に合う。
注意点は以下の通り。
- 同じサイクルを重複して数えないようにする
- 残った辺に id を割り振って、使った辺集合を bit で管理して set に入れると楽
- 自己ループ、多重辺
- グラフ圧縮の過程で発生する
- 自己ループが発生してる頂点は変に削除すると数え漏れが起こるので注意(実装依存)
- 多重辺は、辺に固有 id を割り振っておいて区別すれば OK
ソースコード
#include <bits/stdc++.h> using namespace std; using ll = long long; using pii = pair<int, int>; using graph = vector<set<pii>>; void compress_graph(graph& g, int& uid) { const int n = g.size(); queue<int> que; // remove leaves for(int v = 0; v < n; ++v) { if(g[v].size() == 1u) { que.push(v); } } while(!que.empty()) { const int v = que.front(); que.pop(); if(g[v].size() != 1u) continue; const auto e = *g[v].begin(); g[v].clear(); g[e.first].erase(make_pair(v, e.second)); if(g[e.first].size() == 1u) { que.push(e.first); } } // constraction for(int v = 0; v < n; ++v) { if(g[v].size() == 2u) { que.push(v); } } while(!que.empty()) { const int v = que.front(); que.pop(); if(g[v].size() != 2u) continue; const int a = g[v].begin()->first, a_uid = g[v].begin()->second; const int b = next(g[v].begin())->first, b_uid = next(g[v].begin())->second; if(a == b) continue; // self-loop g[v].clear(); g[a].erase(make_pair(v, a_uid)); g[b].erase(make_pair(v, b_uid)); ++uid; g[a].emplace(b, uid); g[b].emplace(a, uid); if(g[a].size() == 2u) { que.push(a); } if(g[b].size() == 2u) { que.push(b); } } map<int, int> eidx; graph tg(n); for(int v = 0; v < n; ++v) { for(auto& e : g[v]) { if(eidx.count(e.second) == 0) { const int id = eidx.size(); eidx[e.second] = id; } tg[v].emplace(e.first, eidx[e.second]); } } g = move(tg); } int main() { ios_base::sync_with_stdio(false); cin.tie(0); int n, m; cin >> n >> m; graph g(n); int uid = 0; for(int i = 0; i < m; ++i) { int a, b; cin >> a >> b; a--, b--; g[a].emplace(b, uid); g[b].emplace(a, uid); uid += 1; } compress_graph(g, uid); set<ll> cycles; for(int s = 0; s < n; ++s) { if(g[s].empty()) continue; vector<bool> used_v(n); function<void(int, ll)> dfs = [&] (int v, ll used_edge) { used_v[v] = true; for(auto const& e : g[v]) { if(used_edge & (1LL << e.second)) continue; used_edge ^= 1LL << e.second; if(e.first == s) { cycles.insert(used_edge); used_edge ^= 1LL << e.second; continue; } if(!used_v[e.first]) { dfs(e.first, used_edge); } used_edge ^= 1LL << e.second; } used_v[v] = false; }; dfs(s, 0); } cout << cycles.size() << endl; }
感想
こういうのは本番では意外と通しにくい問題という印象がある。