【AtCoder】ABC 448 E - Simple Division

E - Simple Division
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.
AtCoder
実行時間制限: 2 sec / メモリ制限: 1024 MiB / Difficulty: 1359 / NoviSteps: 1D / 配点: 450 点
整数 N,M が与えられるので、 ⌊MN⌋ を 10007 で割った余りを求めよ。
但し、この問題では N は直接与えられず、ランレングス圧縮された形 (ci,li)(i=1,2,…,K) で与えられる。
- 1≤M≤104
- 1≤K≤105
- ci は 0,1,2,3,4,5,6,7,8,9 いずれかの数字
- 1≤li≤109
- c1=0
- M,K,li は整数
N を M で割った余りを R とおくと、以下の等式が成り立つ。
N=⌊MN⌋×M+R(0≤R<M)
ここで、 10007 を法として式変形を行うと、
⌊MN⌋×M≡N−R(mod10007)
が成り立つ。
10007 は素数であり、制約から M≤104 なので、M と 10007 は互いに素。
したがって、10007 を法とする M の逆元 M−1 が存在し、
⌊MN⌋≡(N−R)×M−1(mod10007)(1)
となる。
つまり、 N(mod10007) と R=(N(modM)) を求めることができれば、所望の答えを求めることができることが分かった。
N はランレングス圧縮された形で与えられるので、圧縮区間ごとに以下のように順々に計算していくことができる。
ここで、ni を i 番目の圧縮区間までの N の値とし(n0=0)、 N=nK−1 とする。
なお、以降は 0-indexed で考えることにする。
ni+1=10lini+ci910li−1(2)
導出 (クリックして展開)
サンプルケース1で考えると、 i=3 のとき、n3=316,(c3,l3)=(2,2) となる。
ここで、 n4=31622 は以下のように分解できる。
n4=31622=316×102+22=316×102+2×11=316×102+2×(100+101)=316×102+2×10−11×(102−1)=316×102+2×9102−1
3行目から4行目への変形は、等比数列の和の公式を用いている。
これを一般化すると、式 (2) が導出できる。
mod 演算では、和・差・積に関しては逐次的に計算していくことができるので、任意の法 m に対して、以下のように計算することができる。
ni+1≡10lini+ci910li−1(modm)
しかし、第2項には 9 による割り算が含まれるので、そう単純に計算することはできない。
それを次の2パターンに分けて考える。
このとき、 10007 は素数であるため、 9 と 10007 は互いに素である。
したがって、mod10007 における 9 の逆元 9−1=1112 を用いて、以下のように計算することができる。
ni+1≡10lini+ci(10li−1)×1112(mod10007)
理由 (クリックして展開)
modp における x の逆元 y=x−1 は、以下の式を満たす整数 y である。
xy≡1(modp)
この式に x=9,p=10007 を代入すると、
9yy≡1(mod10007)=10007+1=910008=1112
M は 1≤M≤104 を満たす整数なので、この場合は 9 と M が互いに素とは限らないため、逆元が存在しない場合もある。
どうすればよいのだろうか?
とりあえず、求めたい余りを y と置いてみる:
y≡910l−1(modM)
ここで、910l−1 を M で割った商を k とすると、
910l−110l−1=kM+y=k(9M)+9y
となる。
これは、「10l−1 を 9M で割ると、商が k で余りが 9y になる」ということを示しているから、
10l−1≡9y(mod9M)
と書ける。
今欲しいのは y なので、次のように計算すればよい:
- 10l−1 を 9M で割った余りを計算する。
- その結果を通常の除算として 9 で割る。
以上により、式 (1) は、3つの mod 演算を用いて、以下のように計算することができることが分かった。
- R=N(modM) を modM で計算する。
- N(modM) の一部を mod9M で計算する。
- その他の部分を mod10007 で計算する。
ACLのmodint を用いることで、比較的容易に実装することができる。
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 448
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.
AtCoder
実装時間: 70 分
3種類の mod 演算を用いるので、理論の組み立てで頭がこんがらがりそう。