問題はこちら
1から9までの数字からのみなる、文字列 が与えられる。
次の条件を満たす整数の組 の総数を求めよ。
条件: の 文字目から 文字目までを10進法の整数としてみると、この整数は2019の倍数である。
制約:
考え方
はじめに
を二重ループで全ての通り試して、その数が2019で割り切れるかを試す方法が真っ先に思い浮かぶが、計算量が となり今回は間に合わない。
桁を増やしながら何か数えることができれば、計算量が減りそうだと考える。そこで、桁を一つ増やすごとにどのように状態が変わるかに注目してみる。
簡便化のために、 の長さを 、 の右から 番目の数を とし、を次のように表す。
着目点
今回、「2019という数は10と互いに素な数になっている。なので、 が2019の倍数の時、 も2019の倍数になっている」ことがポイント。
例えば4038000(=2019*2*1000) を考えると、これが2019の倍数の時、4038が2019の倍数になっているということ。当たり前のように感じるが次のように役立つ。
- 4038333のような数が出てきた時、4038333 - 333 = 4038000 が2019の倍数だったならば、4038は2019の倍数となり、題意をみたす。
- 右からどこまでを0にするか?に注目しそうである(右からどこまでを引き算して0にしまうか?)
- 4038333 - 333 % 2019 == 0
- 4038333 % 2019 == 333 % 2019
- 余りの等しい連続する部分列のペアを取ってくれば良い!
言い換え
このことから、次のように言い換えることができる。
- が2019の倍数である。
- が2019の倍数である。(これを とおく。)
- が2019の倍数である。(→累積和と似たような考え方になる。)
同じ余りとなる のペアを探せば、題意を満たす整数の組になるということがわかる。
これは右の桁から順に数字を見ていき、2019で割った余りを とすると、map[m]++
するなどして管理し、最後に各余りについて、二つ取る組み合わせを計算して足し上げれば良い。注意として、余りが0の数の場合、他の数とペアにしなくてもその数だけで題意を満たすため、map[0]=1
と初期化しておくことで他の場合と同じように計算ができる。
計算量は、となり、間に合う。(unordered_map
を使うとlogが取れる。)
この一連の求め方は、A - Zero-Sum Ranges でも同じような流れを踏むので見ておくと良いかもしれない。また、E - Divisible Substringでも似たような問題だったらしいがまだ解いていない。
解答
文字列だと右側から見ていく必要があるために、最初に文字列をreverse
してindexを扱いやすくしている。
また、桁数が非常に大きいため、桁数については別途modを取るようにしている。
#include <bits/stdc++.h> using namespace std; using ll = long long; #define each(i, mp) for(auto& i:mp) int main() { string s; cin >> s; map<ll, ll> mp; mp[0] = 1; reverse(s.begin(), s.end()); ll now = 0, ten = 1; each(e, s) { now = (now + (e - '0') * ten) % 2019; ten *= 10; ten %= 2019; mp[now]++; } ll ans = 0; each(e, mp) { ans += e.second * (e.second - 1) / 2; } cout << ans << '\n'; return 0; }