
配点: 475 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 1296 / NoviSteps: ???
問題概要
整数の組 であって、以下の条件を満たすものの個数を
で割った余りを求めよ。
は相異なる。
を
で割った余りは
に等しい。
制約
は整数
考察
問題文の言い換え
まず、3つ目の条件について考えてみる。これは以下のように言い換えられる。
もし だと
になってしまい、これは2つ目の条件に反する。
また、商と余りの関係から、1つ目の条件と合わせて が言える。
は全て正の整数なので、
という大小関係になる。したがって、2つ目の条件は常に満たされることになる。
は
から自動的に求まることも踏まえると、この問題を以下のように言い換えることができた。変数が1個減った!
- 整数の組
であって、以下の条件を満たすものの個数を
で割った余りを求めよ。
は
の倍数ではない。
個数の数え上げ
では、上述の条件を満たす整数組 を数え上げていこう。
とりあえず と固定すると、
を満たす整数
で
の倍数となるものは、
より、 個存在する。したがって、
の倍数でない
は
個ある。
これを全ての について和を取ればよいので、求める答えは
と書ける。
答えを高速に求める
さて、上の式を愚直に求めていては の計算量となってしまい、本問の制約下では TLE になる。何とかして高速化しなければならない。
とりあえず、答えの式を以下のように分解してみよう。
第1項に関しては、等差数列の和の公式から と
で計算できる。普通にシグマを外してもよい。
問題は第2項だ。
実は、このような「分数の floor sum を求めろ」という問題が ABC230-E で出題されている。
いくつか解法はあるが、結局この部分は で以下のように計算できる(商列挙というやつ)。
で2つに分けて、対称性を利用する。
以上より、求める答えは で計算することができた。
実装例
#include <bits/stdc++.h> using namespace std; #if __has_include(<atcoder/all>) #include <atcoder/all> using namespace atcoder; #endif #define repe(i, start, end) for (auto i = (start); (i) <= (end); (i)++) using ll = long long; using mint = modint998244353; // ======================================== // int main() { ll N; cin >> N; mint sum = 0; ll sqrt_N = floor(sqrt(N)); repe(b, 1, sqrt_N) sum += N / b; mint m_sqrt_N = sqrt_N; mint ans = (mint)N * (N + 1) / 2 - 2 * sum + m_sqrt_N * m_sqrt_N; cout << ans.val() << endl; return 0; }
実装時間: 40分
コメント
商列挙というテクは初めて知った。
こういうのを見ると精進しなきゃなーと思う。