ABC146 C - Buy an Integer

Calendar Clock iconCalendar Clock icon

atcoder

目次

# 問題

https://atcoder.jp/contests/abc146/tasks/abc146_c

# 入力

A B X
  • A, B - 1A,B1091 \leq A, B \leq 10^9を満たす整数
  • X - 1X10181 \leq X \leq 10^18を満たす整数

# 出力

A×N+B×d(N)XA \times N + B \times d(N) \leq Xを満たすような最大のNNを答える問題です.
ただしd(N)d(N)NNの桁数です.

N

# 入出力例

10 7 100
9

# 解説

条件を満たす場合にtrueを返す関数をcanBuyとします.
NNが1以上10910^9以下なので、N=1N = 1からNを1つずつ大きくしていってcanBuyを実行します.
そして初めてfalseを返したらそれより1つ小さい値が答えになります.

これは線形探索なので計算ステップ数的には10910^9となり間に合いません.
10710^7程度に抑える必要があります.

そこでNの性質に着目するとN=1,2,3,...,109N = 1, 2, 3,..., 10^9と単調に増加しています.
これはソート済みの配列とみなすことが出来ます.
ソート済みの配列には二分探索が適用できます.

二分探索の場合は計算量O(log109)O(\log{10^9})なので余裕で間に合います.

二分探索の場合には範囲を漏れなくチェックすることに気をつけることが重要です.
探索範囲の左をleft、右をright、その中央をn、前回のnをprevとします.
更新ロジックと終了条件を以下のようにすると簡単に漏れなくチェックできます.

更新

    1. nが買える時、left = n + 1
    1. nが買えない時、right = n - 1

終了条件は以下どちらかを満たす場合

    1. leftrightが逆転した場合
    1. nprevが同じ場合

範囲外のNNについても事前にチェックしておきましょう.
Nの最大値10910^9が買える場合にはすべてのNNが買えます.
1すら買えない場合には何も買えません.

あとは通常の二分探索です.
以下のコードを参照してください.

# 計算量

整数NNの最大値は10910^9なので、これを二分探索すると、

log109=9log1029.89\log{10^9} = 9 \log{10} \approx 29.89

# 解答

// C++ 14
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <unordered_map>
#include <math.h>

#define ll long long
#define Int int
#define loop(x, start, end) for(Int x = start; x < end; x++)
#define loopdown(x, start, end) for(int x = start; x > end; x--)
#define rep(n) for(int x = 0; x < n; x++)
#define span(a,x,y) a.begin()+x,a.begin()+y
#define span_all(a) a.begin(),a.end()
#define len(x) (x.size())
#define last(x) (*(x.end()-1))

using namespace std;
#define MAX_N 1000000000
#define MIN_N 1
Int A, B, X;

void input() {
  cin >> A >> B >> X;
}

Int d(Int n) {
  return to_string(n).length();
}

bool canBuy(Int n) {
  return A * n + B * d(n) <= X;
}

void solve() {
  // なんでも買える
  if (canBuy(MAX_N)) {
    cout << MAX_N << endl;
    return;
  }

  // 何も買えない
  Int max_n = MIN_N;
  if (!canBuy(max_n)) {
    cout << 0 << endl;
    return;
  }

  // 最高値を二分探索
  Int left = MIN_N+1, right = MAX_N-1;
  Int prev = -1, n;
  while (left <= right) {
    n = (left+right)/2;
    if (n == prev) break;
    prev = n;
    if (canBuy(n)) {
      max_n = n;
      left = n + 1;
    } else {
      right = n - 1;
    }

  }
  cout << max_n << endl;
}

int main(void) {
  input();
  solve();
  return 0;
}

リモートフリーランス。ウェブサービス、スマホアプリエンジニア。
東アジアを拠点に世界を移動しながら活動してます!

お仕事のご依頼・お問い合わせはこちら

コメント