AOJ 2606 : Perm Query
順列はいくつかのサイクルで表記できる。
[l, r)に含まれるサイクルの長さをc[l],...,c[r-1]のように表すと
lcm(c[l], ..., c[r])回だけ、各要素は値が変わる。i番目の要素は
lcm/c[i] 回だけサイクルを回るので、そのサイクルに含まれる数の和をS[i]とすると
i番目の要素についての和は
S[i]*lcm/c[i]
l≦i<rで和を取って
Σ[l≦i<r](S[i]*lcm/c[i])
がクエリ[l, r)に対する解。
問題はlcmの求め方で、lcmは大きい数になりうるのでmod 10^9+7で計算したい。
つまり
[l, r)に含まれるすべてのサイクルの長さについてlcmを計算したい。
[l, r)はO(N)の長さがあって1要素ずつ見ていくと間に合いそうにない。
そこでサイクルの長さごとに見ていく。
サイクルの長さの種類はたかだかO(√N)個しかない。なぜなら、サイクルの長さの和はNなので、
サイクルの長さの種類が最大となるのは
1+2+...+(k-1)+k = N
のような場合であるため。
よって、O(√N)個のすべてのサイクルの長さについて、そのサイクルの出現数を部分和で持っておけば、
各サイクルの長さが[l, r)で出現するかどうかすぐわかる。
事前にサイクルの長さに対して素因数分解しておけば、素因数の数はたかだかO(log(N))個なので
各クエリに対してO(log(N)√N)
全体でO(Qlog(N)√N)
int main(){ ios::sync_with_stdio(false); cin.tie(0); int N, Q; cin >> N >> Q; vi p(N), vis(N); // S[i]/c[i]の部分和 vector<mint> sm(N + 1); // 素因数分解の結果 unordered_map<int, map<int, int> > fa; // lns[i]は長さiのサイクルの出現の部分和 unordered_map<int, vi> lns; rep(i, N)cin >> p[i], p[i]--; rep(i, N)if (!vis[i]) { // 新しいサイクルを見つけた vi a; ll s = 0; int x = i; do { a.push_back(x); vis[x] = 1; // サイクルに含まれる数の和 s += x + 1; x = p[x]; } while (x != i); int len = sz(a); if (!fa.count(len)) { fa[len] = factorize(len); lns[len] = vi(N + 1); } each(y, a) { // S[i]/c[i] sm[y + 1] = mint(s) / len; // 長さlenのサイクルはy番目を含む lns[len][y + 1] = 1; } } // 部分和の計算 rep(i, N)sm[i + 1] += sm[i]; each(q, lns) { vi &v = q.second; // 出現数の部分和 rep(i, N)v[i + 1] += v[i]; } while (Q--) { int l, r; cin >> l >> r; --l; mint s = sm[r] - sm[l], lcm = 1; // 各素因数について、素因数分解したときに最も多く現れたときの指数 unordered_map<int, int> ma; each(q, lns) { int len = q.first; vi &v = q.second; if (v[r] - v[l]) { each(ii, fa[len]) { // ここが最も時間がかかる smax(ma[ii.first], ii.second); } } } // 素因数分解→lcm each(q, ma) while (q.second--)lcm *= q.first; // s=Σ(S[i]/c[i]) cout << s*lcm << endl; } }