こういう問題を求めてた!!
類題として、こんなのがある。
問題概要
長さ の、各文字が '0'〜'9' のいずれかとなっている文字列 と、素数 が与えられる。
の空でない連続する区間であって、その整数が で割り切れるものが何個あるかを求めよ。
制約
- は を満たす素数
考えたこと
文字列の右側から 個分からなる整数を とおくことにする。たとえば、
S = 1533032
であれば、
- = 0
- = 2
- = 32
- = 32 ("032")
- = 3032
という感じにする。こういう風に「区間に関する問題において、累積和的なものを考える」というのは、Zero-Sum Ranges でもおなじみの考え方!!!!!!!
さて、このとき、S の区間 が条件を満たす条件は
と表せることになる。ここで勢いで両辺に をかけて
という風にしてしまいたくなる。この式自体は間違ってない。けど、冷静にならないといけない。この合同式を満たすからといって、元の合同式を満たすとは限らないのだ。
ちなみに、もし仮に「 を満たすような を求めたい」という話に落ちたならば、あとは簡単だ。たとえば S = "152467"、"P = 3" であったら
となるので、
- となるような は、 個
- となるような は、 個
- となるような は、そもそも 2 が 1 回しかないので 0 個
よって答えは、1 + 6+ 0 = 7 個となる。
合同式の式変形について
一般に、 と が互いに素であれば、
⇔
が成立するのだ。一般の場合であっても、
⇒
は成立する。しかし逆が成り立つとは限らない。たとえば
を 10 で割ったあまりは異なるけれど、
となって、 が成立してしまうのだ。でも と が互いに素だったら、掛けたり割ったりして大丈夫!!!!!!
一応、一番下で証明を書いてみる。
元の問題に戻り
そういうわけで、これ。
実は の場合がコーナーケースになっている。この場合は と互いに素ではないのだ。
でもそれ以外の素数 に対しては、 と は互いに素になる。なぜなら を素因数分解すると
となる。素因数に出てくるのは 2 と 5 のみだ。
P が 2, 5 以外の素数のとき
この場合は安心して、上の合同式の両辺に を掛けて良い。そうすると
となる。この場合は、上ですでに述べたように
- を で割ったあまりで分類する
- 各分類ごとにペアの個数を数える
という風にすれば OK。このあたりは、かの有名な下の問題と発想は一緒。
P = 2 のとき
2 の倍数であることは、「下 1 桁が偶数であること」と同値である。よって、区間 [l, r) であって S[r-1] が偶数であるものを数えれば OK。
P = 5 のとき
5 の倍数であることは、「下 1 桁が 0 または 5 であること」と同値である。よって、区間 [l, r) であって S[r-1] が 0 または 5 であるものを数えれば OK。
#include <bits/stdc++.h> using namespace std; long long N; string S; int P; long long solve() { if (P == 2 || P == 5) { long long res = 0; for (int i = 0; i < N; ++i) if ((S[N-i-1]-'0') % P == 0) res += N-i; return res; } vector<long long> val(P, 0); long long tenfactor = 1; long long cur = 0; val[cur]++; for (int i = 0; i < N; ++i) { cur = (cur + (S[N-i-1]-'0') * tenfactor) % P; tenfactor = (tenfactor * 10) % P; val[cur]++; } long long res = 0; for (int p = 0; p < P; ++p) res += val[p] * (val[p] - 1) / 2; return res; } int main() { cin >> N >> P >> S; cout << solve() << endl; }
おまけ:「互いに素」の場合の証明
最後に と が互いに素であれば、
⇔
であることを証明しておく。上から下は明らかなので、下から上を示す。
さて、 が成立するということは、
が で割り切れる
ということになる。 と が互いに素なので、
が で割り切れる
よって が成立する。