2014 Yandex.Algorithm Elimination Stage, Round 2 F - Permutation Cube
解法
X, Y, Z のそれぞれについて,巡回するいくつかのグループに分けることができる.
周期 a で巡回する列と,周期 b で巡回する列と,周期 c で巡回する列を考える.これらはそれぞれ X, Y, Z から持ってくるとする.
このとき,これらの全体としての周期は lcm(a, b, c) である.
これは何を意味するかと言うと,この3つの列のすべての数を網羅するためには,a * b * c / lcm(a, b, c) 回のコスト1の移動が必要であるということである.
したがって,X, Y, Z のそれぞれについて,巡回する部分のサイズをそれぞれ求めて,3重ループですべての3組の巡回列について計算すればよい.
当然これをそのままやるとオーダーがヤバイのだが,同じサイズの巡回列は map でまとめてしまうとオーダーが落ちる.
なぜなら,高々サイズの種類数は sqrt(N) 通りしか無いからである(1 + 2 + ... + N = N * (N + 1) / 2 であるため).
よって,この3重ループは O(Nsqrt(N)) となり,間に合う.
ソースコード
#include <bits/stdc++.h> using namespace std; using ll = long long; ll lcm(ll a, ll b, ll c) { const ll t = a * b / __gcd(a, b); return t * c / __gcd(t, c); } class union_find { public: union_find(int n) : par(n, -1) {} int root(int x) { return par[x] < 0 ? x : par[x] = root(par[x]); } void unite(int x, int y) { x = root(x), y = root(y); if(x == y) return; if(par[x] > par[y]) swap(x, y); par[x] += par[y]; par[y] = x; } int size(int x) { return -par[root(x)]; } private: vector<int> par; }; int main() { int n; cin >> n; vector<int> x(n), y(n), z(n); vector<union_find> uf(3, n); for(int i = 0; i < n; ++i) { cin >> x[i]; uf[0].unite(i, x[i] - 1); } for(int i = 0; i < n; ++i) { cin >> y[i]; uf[1].unite(i, y[i] - 1); } for(int i = 0; i < n; ++i) { cin >> z[i]; uf[2].unite(i, z[i] - 1); } vector<vector<bool>> used(3, vector<bool>(n)); vector<unordered_map<int, int>> cnt(3); for(int i = 0; i < 3; ++i) { for(int j = 0; j < n; ++j) { const int r = uf[i].root(j); if(used[i][r]) continue; used[i][r] = true; cnt[i][uf[i].size(r)] += 1; } } ll ans = 0; for(auto& p1 : cnt[0]) { for(auto& p2 : cnt[1]) { for(auto& p3 : cnt[2]) { const ll sz1 = p1.first, sz2 = p2.first, sz3 = p3.first; const ll cycle = lcm(sz1, sz2, sz3); ans += sz1 * sz2 * sz3 / cycle * p1.second * p2.second * p3.second; } } } cout << ans << endl; }
感想
さすがに典型.油断してるとオーバーフローするのが罠なぐらいか.