ABC 158 E - Divisible Substring
ABC 158 E - Divisible Substring に関連する、合同式の性質 (除法) についてのメモを残します。
合同式の性質 (除法)
入力例 1 : 、、 について考える。
下図のような配列 を用意する。
の連続する部分文字列について、
たとえば、54 は 、354 は 、35 は と表すことができる。
求めたいものは、
を満たす組 の総数である。
10 と が互いに素のとき、
であるから、
を満たす組 の総数を求めればよい。シンプルになった。
合同式の性質 (除法) :
と が互いに素であるとき、
しかし、10 と が互いに素でないとき、すなわち のときは、
は一般には成り立たない。
ただ、これらのときは部分文字列の下一桁に注目することで、 で割り切れるかどうかを判定できる。
下一桁が偶数ならば 2 の倍数。下一桁が 0 または 5 ならば 5 の倍数。
これを使えばよい。
では、10 と が互いに素のときについて、 を満たす の総数を求めたい。
配列 について をとる。
実装を見ると、たとえば 543 を で割った余りを求めるときは、 と分解して考えていることがわかる。
であるから、 の等しい 2 つの要素を選ぶ方法の総数を求めるとよい。
コードは、YouTube 解説放送 を参考に。
#include <bits/stdc++.h> using namespace std; int main() { int N, P; cin >> N >> P; string S; cin >> S; long long ans = 0; if (10 % P == 0) { // P が 2, 5 のとき for (int i = 0; i < N; i++) { if ((S.at(i) - '0') % P == 0) ans += i+1; } } else { vector<int> A(N+1, 0); int ten = 1; for (int i = N-1; i >= 0; i--) { int tmp = (S.at(i) - '0') * ten % P; // S[i] * 10**r A.at(i) = (A.at(i+1) + tmp) % P; ten *= 10; ten %= P; } map<int, long long> M; for (int i = 0; i < N+1; i++) { if (M.count(A.at(i))) M[A.at(i)]++; else M[A.at(i)] = 1; } for (auto p : M) ans += (p.second * (p.second - 1)) / 2; } cout << ans << endl; }