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

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

AtCoder ARC 102 C - Triangular Relationship (300 点)

editorial に載っている解法は整数論 O(1) でやっているし、僕も本番それでやったけど、この制約なら全探索でパッと書けるべきですね...

参考:

問題へのリンク

問題概要 (ARC 102 C / ABC 108 C)

整数  N, K が与えられる。

 a+b,  b+c,  c+a がすべて  K の倍数となるような  1 \le a, b, c \le N を満たす整数  a, b, c の組を数え上げよ。

制約

  •  1 \le N, K \le 2×10^{5}

解法 1: a % K の値で全探索

a % K を決め打つと b % K, c % K のとるべき値が一意に決まることを利用します (a+b, a+c が K の倍数であることから)。そうして決まった b, c について b+c が K の倍数になっているかどうかを check します。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, k; cin >> n >> k;
    vector<long long> num(k, 0);
    for (int i = 1; i <= n; ++i) num[i%k]++; // num[x] = kで割ってxあまる数が1以上N以下に何個あるか
    long long res = 0;
    for (int a = 0; a < k; ++a) {
        int b = (k-a) % k;
        int c = (k-a) % k;
        if ((b+c) % k != 0) continue;
        res += num[a] * num[b] * num[c];
    }
    cout << res << endl;
}

解法 2: O(1) で解く (本番でやった)

整数論的考察をする上では、以下の性質を心から理解しておくと強い。これは初等整数論の理論を構築する上で非常に重要な足がかりとなる定理です:


整数  a, b, m が与えられる。
整数  a, m が互いに素であるとき、 ab m で割り切れるならば、 b m で割り切れる。


合同式で条件を表すと

 a + b ≡ b + c ≡ c + a ≡ 0 ({\rm mod}. K)

となる。まず

 a + b ≡ b + c ({\rm mod}. K)
 a ≡ c ({\rm mod}. K) (両辺から  b を引く)

となることが言える。このことは合同式を用いなくても

  •  a + b K の倍数
  •  c + b K の倍数
  • よって、 (a + b) - (c + b) = a - c K の倍数
  • よって、 a c K で割ったあまりは等しい

という風に考えてもできる。さて、対称性から、

 a ≡ b ≡ c ({\rm mod}. K)

となることがわかる。ここまでで  a + b ≡ b + c ≡ c + a という条件を反映したが、まだ  a + b ≡ 0 という条件は考慮していない。なのでこれを考える:

 a + b ≡ 0 ({\rm mod}. K)
 a + a ≡ 0 ({\rm mod}. K)
 2a ≡ 0 ({\rm mod}. K)

となることがわかる。つまり  2a K で割り切れる。ここで、冒頭の定理を思い出すと、 K 2 が互いに素であってほしいという気持ちになる。なので場合分けする。

K が奇数のとき

 K が奇数なら  K 2 は互いに素なので、 2a K で割り切れることから、 a K で割り切れることとなる。つまり、

 a ≡ b ≡ c ≡ 0 ({\rm mod}. K)

である。つまり、 a, b, c はすべて  K の倍数ということである。答えは、 N 以下の  K の倍数の個数を  x として、 x^{3}

K が偶数のとき

 K = 2K' とおく。そうすると、

 2a 2K' で割り切れる
 a K' で割り切れる

という風になることから、

  •  a ≡ 0 ({\rm mod}. K')
  •  b ≡ 0 ({\rm mod}. K')
  •  c ≡ 0 ({\rm mod}. K')

である。さて、ここから一瞬

  •  a ≡ b ≡ c ≡ 0 ({\rm mod}. K')

であると早とちりしそうになるが、それは違う。まず、

 a ≡ 0 ({\rm mod}. K')
 a ≡ 0 または  K' ({\rm mod}. K)

であることに注意する (例えば  K = 6 のとき、 a は 3 の倍数で、これは  a を 6 で割ったあまりが 0 か 3 であるということと同じである)。 a ≡ b ≡ c ({\rm mod}. K) であることから、

  •  a ≡ b ≡ c ≡ 0 ({\rm mod}. K) または  a ≡ b ≡ c ≡ K' ({\rm mod}. K)

であることが言える。これは  a ≡ b ≡ c ≡ 0 ({\rm mod}. K') とは違う。 a ≡ b ≡ c ≡ 0 ({\rm mod}. K') としてしまうと、

  •  a ≡ 0 ({\rm mod}. K)
  •  b ≡ 0 ({\rm mod}. K)
  •  c ≡ K' ({\rm mod}. K)

みたいなケースも含んでしまうが、こういうのは条件を満たしていない。

さて、あとは数え上げる:

  •  a ≡ b ≡ c ≡ 0 のとき、 N 以下の  K の倍数の個数を  x
  •  a ≡ b ≡ c ≡ K' のとき、 N 以下の  K で割って  K' あまる整数の個数を  y

として、答えは  x^{3} + y^{3} 個になる。

#include <iostream>
using namespace std;

int main() {
    long long n, k;
    cin >> n >> k;
    if (k % 2 == 0) {
        long long a = n / k;
        long long b = (n + (k/2)) / k;
        cout << a*a*a + b*b*b << endl;
    }
    else {
        long long a = n / k;
        cout << a*a*a << endl;
    }
}

さらに

今回の「互いに素」にまつわる整数の整除の定理をつきつめると、以下のことが言える。


合同方程式  ax ≡ b ({\rm mod}. m) を解きたい。

  •  a m が互いに素のとき、 x ≡ a^{-1}b ({\rm mod}. m) と解ける
  • そうでないとき、 a m の最大公約数を  d として、  a = da',  m = dm' とする ( a' m' は互いに素になることに注意)
    •  b d の倍数のとき、 b = db' とすると、元の方程式は  a'x ≡ b' ({\rm mod}. m') に帰着
    •  b d の倍数でないとき、解なし