AOJ 2606 Perm Query

解法

まず,制約を読むと,ある項についてたかだか40回の遷移で元に戻ることがわかるので,愚直にこれを求めます.
このとき,それぞれの項が c[i] 回 で元に戻るとします.
すると,[l, r] が与えられた時,この区間がちょうど最初の状態に戻るのは,L = lcm(c[l], c[l+1], ..., c[r]) 回後であることは明らかです.
そして,各項は L / c[i] 回サイクルを回ることになります.1サイクルでの和s[i] を求めておけば,これは s[i] * L / c[i] を l から r まで足した和が答えになります.

区間の LCM は segment tree で求められるのでそうします.

このままだと O(QNlogN) なのですが,sum[k][i] := (第 i 項までの,サイクルが k であるようなものの s[i] の和)という累積和を持っておけば,k が L の約数であるようなものについてのみ (L / k) * (sum[k][r] - sum[k][l]) を考えて総和を取れば,求めたい答えになるのでそうします.k はたかだか 40 なので間に合います.

ソースコード

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

constexpr ll M = 1e9 + 7;
constexpr int MAX_K = 41;

// セグツリは略

int main() {
    int N, Q;
    cin >> N >> Q;
    vector<ll> p(N);
    for(int i = 0; i < N; ++i) {
        cin >> p[i];
    }

    vector<ll> cycle(N);
    vector<vector<ll>> sum(MAX_K, vector<ll>(N + 1));
    for(int i = 0; i < N; ++i) {
        ll s = p[i];
        ll now = p[i];
        int k = 1;
        for(k = 1; k < MAX_K; ++k) {
            if(now != i + 1) {
                now = p[now - 1];
                s = (s + now) % M;
            } else {
                break;
            }
        }
        cycle[i] = k;
        sum[k][i + 1] = s;
        for(int k = 0; k < MAX_K; ++k) {
            sum[k][i + 1] = (sum[k][i] + sum[k][i + 1]) % M;
        }
    }
    segment_tree<LCM> lcm_seg(cycle);
    while(Q--) {
        int l, r;
        cin >> l >> r;
        l--;
        ll L = lcm_seg.query(l, r);
        ll res = 0;
        for(int k = 1; k < MAX_K; ++k) {
            if(L % k == 0) {
                (res += ((L / k) % M) * (sum[k][r] - sum[k][l] + M)) %= M;
            }
        }
        cout << res << endl;
    }
}