AOJ 2164 - Revenge of the Round Table

解法

回転して同じものはとりあえず重複して数える。
というのも、もし周期 s の並べ方があったとしたら、回転して同じとみなせるものは s 通りだとわかるからだ。

まず、先頭を A に固定して、ループを考慮しない数え上げをする。
次に、ループを考慮して数え上げるが、これは先頭と末尾の A の個数を決め打ちして、間に B から始まって B で終わるものを考えればOK.
これで先頭 A でループも考慮したものが得られて、先頭を B にしたものも同じなので 2 倍する。

さっきので求まったテーブルを dp[i] とすると、周期が i で長さも i であるような列の総数 dp2[i] は
 dp2[i] = dp[i] - (i のすべての約数 j に対して dp2[j] の総和)
となる。

求める答えは、n のすべての約数について dp2[i] / i の総和となる。
計算量は O(N^2)

ソースコード

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

using ll = long long;

constexpr int mod = 1000003;

ll inv(ll n) {
    ll a = n, b = mod, u = 1, v = 0;
    while(b) {
        ll t = a / b;
        a -= t * b; swap(a, b);
        u -= t * v; swap(u, v);
    }
    u %= mod;
    if(u < 0) u += mod;
    return u;
}

int main() {
    int n, k;
    while(cin >> n >> k, n) {
        if(n == 1) {
            cout << 2 << endl;
            continue;
        }
        ll ans = 0;
        if(k >= n) {
            ans += 2;
            k = n - 1;
        }

        vector<vector<vector<int>>> dp1(n + 1, vector<vector<int>>(k + 1, vector<int>(2)));
        dp1[1][1][0] = 1; // start with A
        for(int i = 1; i < n; ++i) {
            for(int j = 1; j <= k; ++j) {
                for(int pre = 0; pre < 2; ++pre) {
                    if(j + 1 <= k) {
                        (dp1[i + 1][j + 1][pre] += dp1[i][j][pre]) %= mod;
                    }
                    (dp1[i + 1][1][!pre] += dp1[i][j][pre]) %= mod;
                }
            }
        }

        vector<ll> dp2(n + 1), sum(n + 1);
        for(int i = 1; i <= n; ++i) {
            for(int j = 0; j <= k; ++j) {
                (dp2[i] += dp1[i][j][1]) %= mod; // ends with B
                (sum[i] += dp1[i][j][0]) %= mod;
            }
            for(int loop = 2; loop <= min(k, i); ++loop) {
                (dp2[i] += 1LL * sum[i - loop] * (loop - 1)) %= mod;
            }
            (dp2[i] += dp2[i]) %= mod; // consider starting with B
        }

        vector<int> dp3(n + 1);
        for(int i = 1; i <= n; ++i) {
            dp3[i] = dp2[i];
            for(int j = 1; j < i; ++j) {
                if(i % j == 0) {
                    (dp3[i] += mod - dp3[j]) %= mod;
                }
            }
        }
        for(int i = 1; i <= n; ++i) {
            if(n % i != 0) continue;
            (ans += dp3[i] * inv(i)) %= mod;
        }

        cout << ans << endl;
    }
}

感想

n == 1 がコーナーケースになってる実装だから良くないなあ。
実装が悪い。