【AtCoder】ABC 448 E - Simple Division

E - Simple Divisionatcoder.jp favicon

実行時間制限: 2 sec / メモリ制限: 1024 MiB / Difficulty: 1359 / NoviSteps: 1D / 配点: 450 点

問題概要

整数 N,MN,M が与えられるので、 NM\left\lfloor \dfrac{N}{M} \right\rfloor1000710007 で割った余りを求めよ。

但し、この問題では NN は直接与えられず、ランレングス圧縮された形 (ci,li)(i=1,2,,K)(c_i, l_i) \: (i = 1, 2, \ldots, K) で与えられる。

制約

  • 1M1041 \le M \le 10^4
  • 1K1051 \le K \le 10^5
  • cic_i0,1,2,3,4,5,6,7,8,90,1,2,3,4,5,6,7,8,9 いずれかの数字
  • 1li1091 \le l_i \le 10^9
  • c10c_1 \neq 0
  • M,K,liM,K,l_i は整数

考察

NNMM で割った余りを RR とおくと、以下の等式が成り立つ。

N=NM×M+R(0R<M)N = \left\lfloor \frac{N}{M} \right\rfloor \times M + R \quad (0 \le R < M)

ここで、 1000710007 を法として式変形を行うと、

NM×MNR(mod10007)\left\lfloor \frac{N}{M} \right\rfloor \times M \equiv N - R \pmod{10007}

が成り立つ。

1000710007 は素数であり、制約から M104M \le 10^4 なので、MM1000710007 は互いに素。 したがって、1000710007 を法とする MM の逆元 M1M^{-1} が存在し、

NM(NR)×M1(mod10007)(1)\left\lfloor \frac{N}{M} \right\rfloor \equiv (N - R) \times M^{-1} \pmod{10007} \tag{1}

となる。

つまり、 N(mod10007)N \pmod{10007}R=(N(modM))R = \left( N \pmod M \right) を求めることができれば、所望の答えを求めることができることが分かった。

NNmod\mathrm{mod} をランレングス圧縮区間ごとに計算する

NN はランレングス圧縮された形で与えられるので、圧縮区間ごとに以下のように順々に計算していくことができる。 ここで、nin_iii 番目の圧縮区間までの NN の値とし(n0=0)(n_0 = 0)N=nK1N = n_{K-1} とする。 なお、以降は 0-indexed で考えることにする。

ni+1=10lini+ci10li19(2)n_{i+1} = 10^{l_i} n_i + c_i \dfrac{10^{l_i} - 1}{9} \tag{2}
導出 (クリックして展開)

サンプルケース1で考えると、 i=3i = 3 のとき、n3=316,(c3,l3)=(2,2)n_3 = 316, (c_3, l_3) = (2, 2) となる。

ここで、 n4=31622n_4 = 31622 は以下のように分解できる。

n4=31622=316×102+22=316×102+2×11=316×102+2×(100+101)=316×102+2×1×(1021)101=316×102+2×10219\begin{align*} n_4 = 31622 & = 316 \times 10^2 + 22 \\ & = 316 \times 10^2 + 2 \times 11 \\ & = 316 \times 10^2 + 2 \times (10^0 + 10^1) \\ & = 316 \times 10^2 + 2 \times \frac{1 \times (10^2 - 1)}{10 - 1} \\ & = 316 \times 10^2 + 2 \times \frac{10^2 - 1}{9} \end{align*}

3行目から4行目への変形は、等比数列の和の公式を用いている。

これを一般化すると、式 (2)(2) が導出できる。


mod\mathrm{mod} 演算では、和・差・積に関しては逐次的に計算していくことができるので、任意の法 mm に対して、以下のように計算することができる。

ni+110lini+ci10li19(modm)n_{i+1} \equiv 10^{l_i} n_i + c_i \dfrac{10^{l_i} - 1}{9} \pmod m

しかし、第2項には 99 による割り算が含まれるので、そう単純に計算することはできない。 それを次の2パターンに分けて考える。

mod=10007(m=10007)\mathrm{mod} = 10007 \: (m = 10007) の場合

このとき、 1000710007 は素数であるため、 991000710007 は互いに素である。 したがって、mod10007\mathrm{mod} \: 10007 における 99 の逆元 91=11129^{-1} = 1112 を用いて、以下のように計算することができる。

ni+110lini+ci(10li1)×1112(mod10007)n_{i+1} \equiv 10^{l_i} n_i + c_i \left( 10^{l_i} - 1 \right) \times 1112 \pmod{10007}
理由 (クリックして展開)

modp\mathrm{mod} \: p における xx の逆元 y=x1y = x^{-1} は、以下の式を満たす整数 yy である。

xy1(modp)x y \equiv 1 \pmod p

この式に x=9,p=10007x = 9, p = 10007 を代入すると、

9y1(mod10007)=10007+1y=100089=1112\begin{align*} 9 y & \equiv 1 \pmod{10007} \\ & = 10007 + 1 \\ y & = \frac{10008}{9} \\ & = 1112 \end{align*}

mod=M(m=M)\mathrm{mod} = M \: (m = M) の場合

MM1M1041 \le M \le 10^4 を満たす整数なので、この場合は 99MM が互いに素とは限らないため、逆元が存在しない場合もある。

どうすればよいのだろうか?

とりあえず、求めたい余りを yy と置いてみる:

y10l19(modM)y \equiv \frac{10^l - 1}{9} \pmod M

ここで、10l19\dfrac{10^l - 1}{9}MM で割った商を kk とすると、

10l19=kM+y10l1=k(9M)+9y\begin{align*} \dfrac{10^l - 1}{9} & = k M + y \\ 10^l - 1 & = k (9M) + 9 y \end{align*}

となる。 これは、「10l110^l - 19M9M で割ると、商が kk で余りが 9y9y になる」ということを示しているから、

10l19y(mod9M)10^l - 1 \equiv 9y \pmod{9M}

と書ける。

今欲しいのは yy なので、次のように計算すればよい:

  1. 10l110^l - 19M9M で割った余りを計算する。
  2. その結果を通常の除算として 99 で割る。

以上により、式 (1)(1) は、3つの mod\mathrm{mod} 演算を用いて、以下のように計算することができることが分かった。

  • R=N(modM)R = N \pmod MmodM\mathrm{mod} \: M で計算する。
  • N(modM)N \pmod{M} の一部を mod9M\mathrm{mod} \: 9M で計算する。
  • その他の部分を mod10007\mathrm{mod} \: 10007 で計算する。

実装例

ACLのmodint を用いることで、比較的容易に実装することができる。

CPP
1.#include <bits/stdc++.h>
2.using namespace std;
3.
4.#if __has_include(<atcoder/all>)
5.#include <atcoder/all>
6.using namespace atcoder;
7.#endif
8.
9.#define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++)
10.
11.using ll = long long;
12.
13.// ======================================== //
14.
15.using mint = static_modint<10007>;
16.using mintM = dynamic_modint<1>;
17.using mint9M = dynamic_modint<2>;
18.
19.int main()
20.{
21. ll K, M;
22. cin >> K >> M;
23. vector<ll> c(K), l(K);
24. rep(i, 0, K) cin >> c[i] >> l[i];
25. mintM::set_mod(M);
26. mint9M::set_mod(9 * M);
27.
28. mint N_mint = 0;
29. mintM R = 0;
30. rep(i, 0, K)
31. {
32. N_mint = N_mint * mint(10).pow(l[i]) + (c[i] * (mint(10).pow(l[i]) - 1) * mint(9).inv());
33. ll r = (mint9M(10).pow(l[i]).val() - 1) / 9;
34. R = R * mintM(10).pow(l[i]) + mintM(r) * c[i];
35. }
36.
37. mint ans = (N_mint - R.val()) * mint(M).inv();
38. cout << ans.val() << endl;
39.
40. return 0;
41.}
Submission #73935969 - AtCoder Beginner Contest 448atcoder.jp favicon

実装時間: 70 分

コメント

3種類の mod\mathrm{mod} 演算を用いるので、理論の組み立てで頭がこんがらがりそう。