floor sum と聞いて!!
問題概要
以上 以下の整数のうち、 で割って 余るものを考える。そのような整数の popcount 値の総和を求めよ。
( ケース与えられる)
制約
考えたこと
TL を見て、floor sum と知った上での考察。
「popcount の総和を求めよ」という設定自体から、とりあえず各桁ごとに求めればよさそうという推測は立つ。
桁目の値が になるものの個数を求めるために、その条件を言い換えていくことにする。まずよくあるのは、
の 桁目が である
を で割ったあまりが 以上 未満である
という言い換え。この言い換えは、たとえば次の問題で本質的に活躍する。この言い換えは苦手で、少し前に頑張って意識したので、今回はスムーズに出た。
これは、次のように言い換えられる。
つまり、元の問題は次のように言い換えられる。
以上 以下の で割って あまる整数 について、
の総和を求めよ。
さらに
とおくと、 の条件は
となる。最終的な答えは、 とおいて、
と表せる。この値は floor sum で求められる。計算量は となる。
コード
#include <bits/stdc++.h> using namespace std; // sum_{i=0}^{n-1} floor((a * i + b) / m) // O(log(n + m + a + b)) long long floor_sum(long long n, long long a, long long b, long long m) { if (n == 0) return 0; long long res = 0; if (a >= m) { res += n * (n - 1) * (a / m) / 2; a %= m; } if (b >= m) { res += n * (b / m); b %= m; } if (a == 0) return res; long long ymax = (a * n + b) / m, xmax = ymax * m - b; if (ymax == 0) return res; res += (n - (xmax + a - 1) / a) * ymax; res += floor_sum(ymax, m, (a - xmax % a) % a, a); return res; } int main() { int CASE; cin >> CASE; while (CASE--) { long long N, M, R; cin >> N >> M >> R; long long range = (N - R) / M + 1; long long res = 0; for (int k = 0; k <= 30; ++k) { long long upper = floor_sum(range, M, R + (1LL<<k), 1LL<<(k+1)); long long lower = floor_sum(range, M, R, 1LL<<(k+1)); res += upper - lower; } cout << res << endl; } }