【AtCoder】ABC 480 E - Product-Bounded Arithmetic Sequences

E - Product-Bounded Arithmetic Sequencesatcoder.jp favicon

実行時間制限: 2 sec / メモリ制限: 1024 MiB / Difficulty: 1355 / NoviSteps: 1D / 配点: 450 点

問題概要

以下の条件を全て満たす正整数列 A=(A1,…,A∣A∣)A=(A_1,\dots,A_{|A|}) を良い数列と定める。

  • ∣A∣≥3|A|\geq 3
  • AA は等差数列である。
  • A2−A1>0A_2-A_1 > 0

良い数列のうち、要素の総積が NN 以下であるものの個数を求めよ。 なお、制約の条件下で答えが 101810^{18} 以下となることが証明できる。

制約

  • 1≤N≤10181 \leq N \leq 10^{18}
  • NN は整数

考察

一見 NN が大きすぎてどうしようもなさそうだが、実は、数列の長さ kk が長くなるほど、条件を満たす初項 aa と公差 dd の組み合わせは急激に減少していく。

例えば、k=3k = 3 の場合、

a(a+d)(a+2d)≤Na(a + d)(a + 2d) \leq N

を満たす組 (a,d)(a, d) の個数を数えればよい。

このとき、d≥1d \geq 1 より a3≤Na^3 \leq N が必要で、1≤a≤N3≤1061 \leq a \leq \sqrt[3]{N} \leq 10^{6} の範囲の整数 aa が探索対象になる。

さらに、 aa を固定すれば総積は dd に関して単調増加するため、二分探索で条件を満たす最大の dd を求めることができる。


これを一般化すると、長さ kk の数列に対して、初項 aa の探索範囲は 1≤a≤Nk1 \leq a \leq \sqrt[k]{N} であり、aa を固定すれば二分探索で条件を満たす最大の公差 dd を求めることができる。

また、初項・公差がともに 1 の場合が積を最小にするので、

1×2×3×⋯×k=k!≤N1 \times 2 \times 3 \times \cdots \times k = k! \leq N

を満たす kk だけ調べれば十分。

20!≈2.43×101820! \approx 2.43 \times 10^{18} なので、 k<20k < 20 まで調べればよい。


最後に計算量について。

長さ kk の数列について、初項は高々 N1kN^{\frac{1}{k}} 通り、二分探索は O(log⁡N)O(\log N) 回、それぞれの判定に O(k)O(k) 時間がかかる。

したがって、全体としては、

O(∑k=320kN1klog⁡N)=O(N13log⁡N)O\left(\sum_{k=3}^{20}kN^{\frac{1}{k}}\log N\right) = O(N^{\frac{1}{3}}\log N)

となり、本問の制約下では十分高速である。

実装例

CPP
1.#include <bits/stdc++.h>
2.using namespace std;
3.
4.#define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++)
5.#define repe(i, start, end) for (auto i = (start); (i) <= (end); (i)++)
6.
7.constexpr long long INFL = 1e+18;
8.
9.using ll = long long;
10.
11.// ======================================== //
12.
13.int main()
14.{
15. ll N;
16. cin >> N;
17.
18. auto check = [&](ll a, ll d, ll l) -> bool {
19. ll prod = 1;
20. rep(i, 0, l) {
21. ll next = a + i * d;
22. if (next > N / prod) return false;
23.
24. prod *= next;
25. }
26.
27. return true;
28. };
29.
30. ll ans = 0;
31. repe(l, 3, 20) {
32. for (ll a = 1; check(a, 1, l); a++) {
33. ll ok = 1, ng = INFL;
34. while (ng - ok > 1) {
35. ll mid = (ok + ng) / 2;
36. if (check(a, mid, l)) ok = mid;
37. else ng = mid;
38. }
39.
40. ans += ok;
41. }
42. }
43.
44. cout << ans << endl;
45.
46. return 0;
47.}
atcoder.jp favicon

実装時間: 40分

コメント

kk が小さい場合で実験してみると、意外と途方もない問題ではないことが分かって拍子抜け。