SRM 668 div1 med:WalkingToSchool
解法
この問題では閉路が重要なポジションを占めています。
例えば, サンプル 1) を見てみましょう。
このグラフでは, (0, 1) という閉路と, (0, 1, 2) という閉路があります。
この閉路の長さ 2 と 3 の最大公約数は 1 です。なので, 十分大きな K に対して, 長さ K で 0 -> 1 に行く方法及び 1 -> 0 に行く方法があるわけです。
これをメインアイデアとして, 更に具体的なところを詰めます。
まず, 各頂点からはどの閉路を使えるのかを調べます(dfs でやっているところ)。これは, 各頂点 s をスタートとして, ぐるっと回って再び頂点 s にたどり着くのにかかる長さを調べれば良いです。結局大事なのは最大公約数なので, それのみを覚えておきます。
次に, 各頂点から, どの頂点にたどり着けるのかを調べます(cfs でやっているところ)。これらを調べることにより, 頂点 0/1 から, どの閉路を利用できるのかがわかります。
例えば頂点 0 から頂点 1 に向かうときには, 0 -> 頂点 i -> 1 というような軌跡をたどります。
そのため, 頂点 i を通るループを利用するためには,
・0 から i に行くことができる
・i から 1 に行くことができる
という条件を満たしている必要があります。この条件を満たしている場合は, その閉路を使って長さを調整できます。
得た知見
- 閉路検出は O(NM) でできる
- 割と「閉路検出どうやろうかー」と悩んでいた
vector<int> G[2020]; int loop[2020]; bool visit[2020]; void dfs(int v, int s, int d) { visit[v] = true; for (int to : G[v]) { if (to == s) { loop[s] = __gcd(loop[s], d+1); } else if (visit[to]) continue; else { dfs(to, s, d+1); } } } bool can[2020][2020]; void cfs(int v, int s) { can[s][v] = true; for (int to : G[v]) { if (can[s][to]) continue; cfs(to, s); } } class WalkingToSchool { public: string canWalkExactly(int N, vector <int> from, vector <int> to) { //cout << "TEST" << endl; const string C = "Chores", F = "Freedom"; int M = from.size(); for (int i = 0; i < N; i++) { G[i].clear(); loop[i] = 0; } for (int i = 0; i < M; i++) { G[from[i]].push_back(to[i]); } for (int i = 0; i < N; i++) { memset(visit, false, sizeof(visit)); dfs(i, i, 0); } //for (int i = 0; i < N; i++) cout << loop[i] << endl; memset(can, false, sizeof(can)); for (int i = 0; i < N; i++) { cfs(i, i); } { int g = 0; for (int i = 0; i < N; i++) { if (!can[0][i] || !can[i][1]) continue; g = __gcd(g, loop[i]); } if (!can[0][1] || g != 1) return C; } { int g = 0; for (int i = 0; i < N; i++) { if (!can[1][i] || !can[i][0]) continue; g = __gcd(g, loop[i]); } if (!can[1][0] || g != 1) return C; } return F; } };