けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AOJ 3211 Symmetric Nbase Number (OUPC 2020 C)

初手実験に走れたのは割とよかった。そういう戦い方もできるようになってきた。

問題概要

正の整数  X を 先頭に 0 を含まないように  B 進数表現したときの各位の数を、小さい位から順に要素とした数列を  f(X, B) とする。次のような感じ。

  •  f(10,2)=(0, 1, 0, 1)
  •  f(41,−4)=(1, 2, 3)

正の整数  A, B が与えられる。 1 \le a \le A,  2 \le b \le B を満たす整数組  (a, b) であって、

 f(a, b) = f(a, -b)

を満たすものの個数を 998244353 で割ったあまりを求めよ。

制約

  •  1 \le A \le 10^{18}
  •  2 \le B \le 10^{5}

考えたこと

まずは  f(a, b) = f(a, -b) という条件をわかりやすく言い換えることが急務となる。実験によって、次のことがわかった。


 a b 進法で表したときに、

  • 桁数が奇数である
  • 偶数桁目の値がすべて 0 である

という条件を満たすことが必要十分である。


たとえば、 a = (30205) などは条件を満たす。ちゃんとした証明は editorial を参照。

ningenme.hatenablog.com

あとは  b を固定したときに、 a を素早く数え上げられれば OK。桁 DP だったり、その他いろんな方法が考えられそう。ここでは、

  •  a 以下の整数のうち、条件を満たす最大の整数を  B 進法で求めて
  • それを偶数番目を除去したものを 10 進法に直す

という方法でやってみた。

コード

#include <bits/stdc++.h>
using namespace std;

const int MOD = 998244353;
int main() {
    long long A, B;
    cin >> A >> B;
    long long res = 0;

    auto solve = [&](long long b) -> long long {
        vector<long long> vec;
        long long A2 = A;
        while (A2) vec.push_back(A2 % b), A2 /= b;
        reverse(vec.begin(), vec.end());

        if (vec.size() % 2 == 0) {
            vec.pop_back();
            for (int i = 0; i < vec.size(); ++i) {
                if (i % 2 == 0) vec[i] = b-1;
                else vec[i] = 0;
            }
        } else {
            bool change = false;
            for (int i = 0; i < vec.size(); ++i) {
                if (i % 2 == 1) {
                    if (vec[i] != 0) {
                        change = true;
                        vec[i] = 0;
                    }
                }
                else if (change) vec[i] = b-1;
            }
        }

        long long res = 0;
        for (int i = 0; i < vec.size(); ++i) {
            if (i % 2 == 1) continue;
            res = (res * b) + vec[i];
        }
        return res;
    };
        
    for (long long b = 2; b <= B; ++b) res = (res + solve(b)) % MOD;
    cout << res << endl;      
}