解法.冪乗法
解説 を参考にした.
等差数列の要素 を十進数表記したときの桁数を とすると,等差数列を一列に並べた整数 は次のように表される.
ただし,この式を愚直に計算すると 時間かかるので高速に計算することを考える.
桁数が となる要素をまとめて計算する.ここで,与えられた数列は等差数列なので桁数が等しい要素はもとの数列の連続した部分数列となる.また,この部分数列は等差数列と公比が の等比数列の積となる(等差×等比数列 - Wikipedia).等差 x 等比数列の一般項は知られているが,ここでは連立漸化式を立てて行列の冪乗法に帰着して解く.
まず, となる添字 を求めると,
・ より ,
・ より
となる.ただし,実際の と はそれぞれ上で計算した値と 0 の最大値と の最小値をとる.
ここで, を一列に並べた整数 を求める. を初期値として,漸化式を と定義すると, は部分数列 の 1 項目から 項目を一列に並べた整数と等しくなり, は部分数列の 項目となる.したがって,この連立漸化式を解き を求めると,この値は と等しいので答えが求まる.
連立漸化式(連立漸化式の3通りの解き方 | 高校数学の美しい物語)は漸化式を行列で表現して冪乗法で求めると 時間で求まる.
後は,等差数列の要素が 未満なので, として整数 を求めて, の桁数を としたとき, の による剰余を求めればよい. は部分数列の左添字から分かる.
計算時間:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using Matrix = vector<vector<ll>>;
inline ll ceil_ll(ll x, ll y) { return (x + y - 1) / y; }
Matrix mulMatrix(const Matrix &A, const Matrix &B, const ll MOD) {
Matrix C(3, vector<ll>(3));
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 3; ++k) {
(C[i][j] += (A[i][k] * B[k][j]) % MOD) %= MOD;
}
}
}
return C;
}
Matrix powMatrix(Matrix A, ll e, const ll MOD) {
vector<vector<ll>> X(3, vector<ll>(3));
X[0][0] = X[1][1] = X[2][2] = 1;
for ( ; 0 < e; e >>= 1) {
if (e & 1) X = mulMatrix(X, A, MOD);
A = mulMatrix(A, A, MOD);
}
return X;
}
ll fast_pow10(ull e, const ull MOD) {
ull a = 10, x = 1;
for ( ; 0 < e; e >>= 1) {
if (e & 1) (x *= a) %= MOD;
(a *= a) %= MOD;
}
return x;
}
int main() {
cin.tie(0); ios::sync_with_stdio(false);
constexpr int DIGITS = 19;
vector<ll> pow10(DIGITS);
pow10[0] = 1;
for (int i = 1; i < DIGITS; ++i)
pow10[i] = 10 * pow10[i - 1];
ll L, A, B, M;
cin >> L >> A >> B >> M;
vector<ll> begin(DIGITS), num(DIGITS);
for (ll d = 1; d < DIGITS; ++d) {
begin[d] = max(0ll, ceil_ll(pow10[d - 1] - A, B));
const ll end = min((pow10[d] - A - 1) / B, L - 1);
const ll s_l = A + B * begin[d], s_r = A + B * end;
if (pow10[d - 1] <= s_l && s_r < pow10[d] && begin[d] <= end)
num[d] = end - begin[d] + 1;
}
ll ans = 0;
for (ll d = 1; d < DIGITS; ++d) {
if (num[d] == 0) continue;
Matrix X(3, vector<ll>(3));
X[0][0] = pow10[d] % M; X[0][1] = 1;
X[1][1] = 1; X[1][2] = B % M;
X[2][2] = 1;
X = powMatrix(X, num[d], M);
const ll s0 = (A + B * begin[d]) % M;
const ll s = ((X[0][1] * s0) % M + X[0][2]) % M;
(ans *= fast_pow10((ull)d * num[d], M)) %= M;
(ans += s) %= M;
}
cout << ans << endl;
return 0;
}
解説を見た後なのに 21WA をしたので反省.
部分数列の右添字が間違いなどの細かいミスはすぐ見つけたけど, のオーバーフローがなかなか見つからなかった.
long long
だとオーバーフローで unsigned long long
にしたら通った(ツライ💧).
あとで考えると のときとかに次のようなケースで落ちる.
900000000000000000 9999999999999999 1 1000000000
実装はもう少し簡潔に書けるけど諦め.
はじめ多項式とか考えていたけど,桁数でまとめるのはなるほどで面白かった.