ntk log ntk

競プロや非競技プロのやったことを書いています.

Codeforces Round #506 (Div. 3) D. Concatenated Multiples

D. Concatenated Multiples

問題概要

正整数nkと,n個の正整数aが与えられる.a_ia_jを連結させた数のうち,kで割り切れるペア(i, j)の個数を答えよ.ただしi \neq j.連結させた数の例としては,a_j = 12a_i = 654なら12654a_j = 487a_i = 3なら4873とする.

解法パートで,a_ja_iを連結させた数をa_j a_iという風に書く.

解法

a_iを,mod k0にしたいと考える.a_ik - a_iを足したら,mod k0になる.正整数a_iは高々2 \times 10^5個あるので,一つのa_iに対して条件を満たすk - a_iが何個あるかを高速に求めたい.

ここで,d(a_i)a_iの桁数とする.d(12) = 2d(12345678) = 8と言った感じ.こうすると,a_ja_iを固定し,a_j a_iという連結させた数がa_j \times 10^{d(a_i)} + a_iと表せる.例えば,a_j = 57a_i = 332とすると,d(332) = 3であって57332 = 57 \times 10^3 + 332と書ける.

上のように書いて何がうれしいかと言うと,和の前と後でバラバラにmodを取れることである.つまり,(a_j \times 10^{d(a_i)} \% k)+ (a_i \% k)というように計算できる.上の例では,k = 11とすると57332 \% 11 = ( (57 \times 10^3) \% 11 + (332 \% 11) ) \% 11 = (2 + 9) \% 11 = 11 \% 11 = 0となる.

最初にしたかったことを思い出す.一つのa_iに対して条件を満たすk - a_iが何個あるかを高速に求めたい.前処理として,(a_j \times 10^{d(a_i)} \% k)を取り得るd(a_i)に対してすべて計算し,何個あるかカウントしておくと最初にしたかったことができる.

d(a_i)が取り得る値の範囲を考える.こういうときは制約を見返すとよくて,1 \le a_i \le 10^9である.したがって,a_iは高々10桁にしかならないので,1 \le d(a_i) \le 10である.

このようにして各a_jに対して,(a_j \times 10^{d(a_i)} \% k)1 \le d(a_i) \le 10の範囲ですべて計算してから個数カウントする前処理をした後で,各a_iごとに k - a_iの個数を足し合わせたら,答えが求まる.

TLが2secだとギリギリだったり,(k - a_i)a_i \times 10^{d(a_i)}が同じになるとき(これをカウントしてしまうとa_ia_iのペアを数えることになる)に注意しながら実装する.

実装

コード

// #define _GLIBCXX_DEBUG
// #pragma GCC optimize("Ofast")
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <bits/stdc++.h>
// #include <atcoder/all>
#define rep(i, n) for (int i = 0; i < (int)(n); ++i)
#define reps(i, s, n) for (int i = s; i < (int)(n); ++i)
using namespace std;
using ll = long long;
template <class T> using V = vector<T>;
template <class T> using VV = V<V<T>>;
template <class T> inline bool chmax(T& a, T b) {
    if (a < b) {
        a = b;
        return true;
    }
    return false;
}
template <class T> inline bool chmin(T& a, T b) {
    if (a > b) {
        a = b;
        return true;
    }
    return false;
}
constexpr int INF = 1e9 + 7;
constexpr ll LINF = 1LL << 60;

int calc_digit(ll a) {
    int ret = 0;
    while (a > 0) {
        ret++;
        a /= 10;
    }
    return ret;
}

void solve() {
    ll n, k;
    cin >> n >> k;
    V<ll> A(n);
    rep(i, n) cin >> A[i];

    VV<ll> val(11, V<ll>(n));
    rep(i, n) {
        ll v = A[i];
        rep(d, 11) {
            val[d][i] = v;
            v *= 10;
            v %= k;
        }
    }
    map<ll, ll> cnt[11];
    rep(d, 11) {
        rep(i, n) { cnt[d][val[d][i]]++; }
    }
    ll ans = 0;
    rep(i, n) {
        int d = calc_digit(A[i]);
        ll need = (k - A[i] % k) % k;  // A[i] % k = 0のときがあるので,外側でも % kする
        ans += cnt[d][need];
        if (need == val[d][i]) ans--;
    }
    cout << ans << "\n";
}
int main() {
    cin.tie(nullptr);
    // ios_base::sync_with_stdio(false);
    // cout << fixed << setprecision(15);
    solve();
}

提出コードへのリンク

サンプル

# input
6 23
45 1 10 12 11 7
# output
1

以下三枚,サンプルを考えるときのメモ(手書きで読みにくかったらすみません).

f:id:ntk_ta01:20200917101906j:plain

f:id:ntk_ta01:20200917101928j:plain

f:id:ntk_ta01:20200917101942j:plain

1012だけがmod k0になる.


mapのアクセスのしかたを変えると,TLEして苦戦した.メモリの局所性を感じる.あとmapを要素とした配列を初めて使った.Pythonならdictを要素としたリスト.言語をあっちこっち使うと混乱しそうになる.