けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ARC 050 C - LCM 111 (試験管黄色)

レピュニット数を題材とした問題。

問題概要

 1 A 個並べた数と、 1 B 個並べた数の最小公倍数を  M で割ったあまりを求めよ。

制約

  •  1 \le A, B \le 10^{18}
  •  1 \le M \le 10^{9}

考えたこと

 1 を並べた数をレピュニット数とよぶ。数学的に面白い性質が色々ある。

レピュニット数の最大公約数は、実は  1 \mathrm{GCD}(A, B) 個並べた数になる。このことを Euclid の互助法に基づいて検証する。

たとえば  111111 ( 1 6 個) と、 1111 ( 1 4 個) の最大公約数を求めたいとする。

 111111 - 1111 = 110000

であり、 10 11...1 は互いに素なので、

 \mathrm{GCD}(111111, 1111) = \mathrm{GCD}(11, 1111)

になると言える。一般に、 A \gt B のとき、

 \mathrm{GCD}(1 が A 個, 1 が B 個) = \mathrm{GCD}(1 が A - B 個, 1 が B 個)

が成立する。この  A, B の部分の変化が Euclid の互助法における変化に他ならない。よって、上に述べたことが成立する。

最小公倍数を求める

 1 A 個並べた数を  f(A) と表すことにすると、求めたい数は  G = \mathrm{GCD}(A, B) として、

 \displaystyle \frac{f(A)f(B)}{f(G)} \pmod m

ということになる。 m が素数とは限らないので、 f(G) で割るという演算は回避したい。そこで、

  •  f(A) = 1 + 10 + 10^{2} + \dots + 10^{A-1}
  •  \displaystyle \frac{f(B)}{f(G)} = 1 + 10^{G} + 10^{2G} + \dots + 10^{(\frac{B}{G}-1)G}

であることに着目してこれを求めることにする。

全体として、計算量は  O(\log A + \log B) となる。

コード

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

// a^n mod m
long long pow(long long a, long long n, long long m) {
    if (n == 0) return 1;
    long long t = pow(a, n/2, m);
    t = t * t % m;
    if (n & 1) t = t * a % m;
    return t;
}

// 1 + a + ... + a^{n-1} mod m
long long ser(long long a, long long n, long long m) {
    if (n == 0) return 0;
    long long res = ser(a * a % m, n/2, m);
    res = res * (a + 1) % m;
    if (n & 1) res = (res * a + 1) % m;
    return res;
}

int main() {
    long long A, B, M;
    cin >> A >> B >> M;

    long long G = gcd(A, B);
    long long fA = ser(10, A, M);
    long long fBdivfG = ser(pow(10, G, M), B/G, M);
    cout << fA * fBdivfG % M << endl;
}