2026年10月15日木曜日

ABC146C - Buy an Integerを二分探索で解いてみた

40代文系社内SEが、AtCoder Beginner Contest 146 C - Buy an IntegerをCommon Lispで解いてみました。

まず問題文を見ます。
1以上10の9乗以下の整数のうち、$A \times N + B \times d(N) \le X$を満たす最大の数$N$を求める問題です。
問題文の時点で1から順番に試していく方法は使えないことがわかります。
二分探索であれば、$O(\log N)$で間に合うので二分探索を使う方向で考えます。

次に制約をみますが、特に方針に影響はなさそうです。

問題文の式から、整数が大きくなると値段も高くなることがわかります。
整数をひとつも買えないときは$0$を返すというところを少し読み替えて、商品に$0$を追加し、常に購入できるとします。
また、絶対に購入できない数として$10^9+1$を商品に追加します。
こうすることで、途中で条件を満たさなくなる単調性のある数列を用意できました。


あとは二分探索で答えを求めます。

(let* ((a (read))
       (b (read))
       (x (read)))

  (labels ((cost (n)
             ;; 代金を計算する
             (+ (* a n)
                (* b (length (format nil "~A" n)))))

           (bsearch (min max)
             ;; 購入できる最大の整数を二分探索する
             (if (= (1+ min) max)
                 min
                 (let ((mid (+ min (floor (- max min) 2))))
                   (if (<= (cost mid) x)
                       (bsearch mid max)
                       (bsearch min mid))))))

    (format t "~A~%" (bsearch 0 (1+ (expt 10 9))))))
  

AtCoder Beginner Contestを解いてみたリスト

2026年10月12日月曜日

ABC478C - Sort Subarrayを解いてみた

40代文系社内SEが、AtCoder Beginner Contest 478 C - Sort SubarrayをCommon Lispで解いてみました。

まず問題文を見ます。
数列の一部を昇順に並べ替えることで、数列全体が昇順になるか判定する問題です。
最初に思いついた方法としては、並べ替える範囲をずらしながら並べ替え、全体が昇順になっているか判定するという方法です。計算量が$O(N^2)$になるので、$N \le 10^4$までなら採用できます。

次に制約を見ます。
$N$の最大値が$2 \times 10^5$なので、上で書いた方法では間に合いません。残念。
さらに考察をすると、数列を3つの部分に分けられることに気が付きました。操作によって並べ替えられる部分と、その両側の並べ替えられない部分です。この並べ替えられない部分の要素数を$N$から引いた値が$K$以下であれば、問題文の条件を満たすことになります。

並べ替えられない部分の要素数を数えるには、$A$と全体を昇順でソートした$A$を比較し、値が変わっていない要素を数えれば求められます。計算量は$O(N)$です。

以上より、以下の手順で解きます。

  1. $A$のコピーを作成し、昇順でソートする
  2. 先頭から$A$とコピーを比較し、値が一致するものを数える
  3. 末尾から$A$とコピーを比較し、値が一致するものを数える
  4. $N$から上の2,3で数えた値を引いた値が$K$以下のとき、条件を満たすと判定する
(let* ((n (read))
       (k (read))
       (a (make-array n)))

  (loop for i from 0 below n
        do (setf (aref a i) (read)))

  (let ((copy-a (copy-seq a))
        (count-l 0)
        (count-r 0))
    ;; コピーしたAを昇順にソート
    (sort copy-a #'<)

    ;; ソート前後で値が変わらない要素を数列の先頭から数える
    (loop for i from 0 below n
          for ai = (aref a i)
          for copy-ai = (aref copy-a i)
          while (= ai copy-ai)
          do (incf count-l))

    ;; ソート前後で値が変わらない要素を数列の末尾から数える
    (loop for i from (1- n) downto 0
          for ai = (aref a i)
          for copy-ai = (aref copy-a i)
          while (= ai copy-ai)
          do (incf count-r))

    (format t "~:[No~;Yes~]~%" (<= (- n count-l count-r) k))))
  

2026年10月9日金曜日

ABC197C - ORXORをビット全探索で解いてみた

40代文系社内SEが、AtCoder Beginner Contest 197 C - ORXORをCommon Lispで解いてみました。

まず問題文を見ます。
数列$A$をいくつかのグループに分け、グループ内で$\mathrm{OR}$を計算します。さらに、得られた値の$\mathrm{OR}$を計算します。
グループの分け方によって$\mathrm{XOR}$で得られる値が変わるので、その最小値を求める問題です。
等しい値の$\mathrm{XOR}$は0になるので、すべてのグループが同じ値になれば最小になりますが、できるだけ小さくする方法はちょっと見当もつきません。

次に制約を見ます。
$N$の最大値が20となっているので、ビット全探索なら$2^{20}=1048576$なので使えるかもしれません。
ビット全探索で解くとすると、何をビットで表現するかが問題になります。
ここで私が思いついたのは、壁をビットで表現する方法です。
数列$A$をグループ化するという部分を、数の間に壁を置くと読み替え、置く/置かないをビットで表現する方法です。
壁の設置数は、最大で$N-1$になります。

例えば入力例1であれば、

  • $(0)_{10}=(00)_2$のとき、壁を置かずに全体を1つのグループとする
  • $(1)_{10}=(01)_2$のとき、5と7の間に壁を置き、1,5と7に分ける
  • $(2)_{10}=(10)_2$のとき、1と5の間に壁を置き、1と5,7に分ける
  • $(3)_{10}=(11)_2$のとき、各数の間に壁を置き、すべて別のグループとする

$(2)_{10}=(10)_2$のときのイメージを以下に示します。

計算量はビット全探索の$2^N$と、それぞれのパターンでORXORの計算する$N$で全体で$O(2^N \times N)$となり、最大$20971520$で、間に合いそうです。

(let* ((n (read))
       (a (make-array n)))

  (loop for i from 0 below n
        do (setf (aref a i) (read)))

  (loop for b from 0 below (expt 2 (1- n))
        for ors = (loop with temp-ors = nil
                        with temp-or = 0
                        for i from 0 below n
                        do (setf temp-or (logior temp-or (aref a i)))
                           (when (or (= i (1- n))
                                     (logbitp i b))
                             (push temp-or temp-ors)
                             (setf temp-or 0))
                        finally
                           (return temp-ors))
        minimize (reduce #'logxor ors)
          into it
        finally
           (format t "~A~%" it)))
  
制約からアルゴリズムを推測し、問題を解くことができました。

AtCoder Beginner Contestを解いてみたリスト

2026年10月4日日曜日

ABC212C - Min Differenceを二分探索で解いてみた

40代文系社内SEが、AtCoder Beginner Contest 212 C - Min Differenceを解いてみました。

まず問題文を見ます。

$A$と$B$の2つの数列から数値を選んだときの差の最小値を求める問題です。

同じ数値が両方に含まれていると、答えは$0$になります。

全探索すれば答えは出ますが、C問題ですし全探索では間に合わないでしょう。

次に制約を見ます。

$1 \le N, M \le 2 \times 10^5$なので、予想通り全探索$O(NM)$では間に合いません。

$O(N \log M)$なら間に合うので、二分探索が使えないか考えます。

まず、数列の並び順については何も記載されていないのでソートが必要になりますが、ソートの計算量は$O(N \log N)$なので、入力から読み込んだあとで1回だけソートすれば問題ありません。

次に、差の最小値については、$A$から選んだ値($A_i$)以上という条件で$B$を二分探索します。一致する数値があれば$0$が答えになり、そうでなければ$A_i$より小さい数値の最大値か、$A_i$より大きい数値の最小値のどちらかとの差が答えになります。

以上より、以下の手順で解きます。

  1. $B$をソートする
  2. $A$から数値を順番に選択し、$A_i$とする
  3. $B$から$A_i$以上の値を二分探索で検索する
  4. $A_i$未満の最大値と$A_i$との差の絶対値と、$A_i$以上の最小値と$A_i$との差の絶対値のより小さい方を記録する
  5. 4で記録した数値の最小値を答えとする
Common Lispによる実装例

(let* ((n (read))
       (m (read))
       (a (make-array n :initial-contents (loop repeat n collect (read))))
       (b (make-array m :initial-contents (loop repeat m collect (read)))))

  (sort b #'<)

  (labels ((bsearch (ai &optional (l -1) (r m))
             (if (= (1+ l) r)
                 l
                 (let ((mid (+ l (floor (- r l) 2))))
                   (if (< (aref b mid) ai)
                       (bsearch ai mid r)
                       (bsearch ai l mid))))))

    (loop for ai across a
          for l = (bsearch ai)
          minimize (cond ((= l -1)
                          (abs (- (aref b 0) ai)))
                         ((= l (1- m))
                          (abs (- (aref b l) ai)))
                         (t
                          (min (abs (- (aref b l) ai))
                               (abs (- (aref b (1+ l)) ai)))))
            into it
          finally
             (format t "~A~%" it))))

二分探索の結果が$B$の両端になるときだけ少し注意が必要です。

AtCoder Beginner Contestを解いてみたリスト

2026年10月2日金曜日

ABC175C - Walking Takahashiを解いてみた

40代文系社内SEが、AtCoder Beginner Contest 175 C - Walking Takahashiを解いてみました。

まず問題文をみます。

座標$X$にいる高橋君をできるだけ原点に近づけるように移動させると、どこまで近づけることができるかという問題です。

移動には距離($D$)と回数($K$)の制限があります。

次に制約をみます。

$1 \le K \le 10^{15}$より、愚直に移動を繰り返すとTLEになります。

まず、問題を単純にするためにXが負の値のときは$-1$を掛けて正の値にします。

次に高橋君を原点を超えない範囲で負の方向に移動し、その座標を$G$とします。このとき、$G$を引き算で求めようとすると$O(K)$で間に合わないので割り算を使います。

移動回数: $X \div D$

$G$: $X - (D \times 移動回数)$

この時点で移動回数を使い切ったとき、$G$が答えになります。

まだ移動回数が残っているとき、$G$とさらに1回だけ負の方向に移動した座標を往復することになります。すなわち、残りの移動回数が偶数であれば$G$が答えになり、奇数であれば$G$引く$D$の絶対値が答えになります。

(let* ((x (read))
       (k (read))
       (d (read)))
  ;; 単純化するためにxを正の値にそろえる
  (when (< x 0)
    (setf x (* x -1)))

  ;; できるだけ原点に近づく
  (let* ((move-count (min k (floor x d)))
         (g          (- x (* d move-count))))
    (cond ((zerop (- k move-count))
           ;; これ以上移動できないとき
           (format t "~A~%" g))
          ((oddp (- k move-count))
           ;; 残りの移動回数が奇数のとき
           (format t "~A~%" (abs (- g d))))
          (t
           ;; 残りの移動回数が偶数のとき
           (format t "~A~%" g)))))

2026年9月27日日曜日

ABC154D - Dice in Lineを解いてみた

40代文系社内SEがAtCoder Beginner Contest 154 D - Dice in Lineを解いてみました。

まず問題文を見ます。

期待値を求め、合計を計算し、さらに最大値を求めると。$N$個から$K$個の合計を計算することになるので、愚直に計算すると$O(NK)$となるのでおそらくTLEになるでしょう。

次に制約を見ます。

$1 \leq N \leq K \leq 200000$ということで、やはり期待値の合計を計算するところは工夫が必要です。正$1000$面体のサイコロはほぼ球になる気がします。出目がわかりにくそう。

さて、解き方についてですが、まず期待値についてはサイコロの目をすべて合計して面数で割ります。

$1$から$K$までの合計を計算するには、$(1 + K) \times K \div 2$を使えば$O(1)$で求められます。例えば、一般的な正六面体の場合は、$(1 + 6) \times 6 \div 2 = 21$となります。

次に$N$個から$K$個の合計を計算するところですが、「連続する$K$個」というところがポイントです。まず先頭$K$個の合計を求め、合計1とします。合計2を求めるには、合計1に$K+1$個目の値を足し、1個目の値を引くことで$O(1)$で求められます。

例えば、入力例1では以下のようになります。

合計1: $1.0 + 1.5 + 1.5 = 4.0$
合計2: $4.0 + 2.5 - 1.0 = 5.5$
合計3: $5.5 + 3.0 - 1.5 = 7.0$

以上より、以下の手順で解きます。

  1. 各サイコロの期待値を計算する
  2. $K$個の期待値の合計を計算する
  3. 期待値の合計の最大値をとる
(defun solve ()
  (let* ((n (read))
         (k (read))
         (p (make-array n :initial-contents (loop repeat n collect (read))))
         (q (make-array n)))
    (loop for i from 0 below n
          for p-i = (+ (aref p i) 0d0)
          do (setf (aref q i) (if (= p-i 1)
                                  1
                                  (/ (* (1+ p-i) p-i) 2 p-i))))
    (loop with a = 0
          for i from 0 below n
          for qi = (aref q i)
          maximize (progn
                     (incf a qi)
                     (when (>= (- i k) 0)
                       (decf a (aref q (- i k))))
                     a)
            into it
          finally
             (format t "~,10F~%" it))))

(solve)

0d0は、倍精度浮動小数点数で計算するために使用しています。

2026年9月25日金曜日

ABC141D - Powerful Discount Ticketsを優先度付きキューで解いてみた

40代文系社内SEがAtCoder Beginner Contest 141 D - Powerful Discount Ticketsを解いてみました。

まず問題文をみます。

半額 小数点以下切り捨て になる割引券という夢のような設定です。それって最終的には0円もありえる?と思ったら入力例3にありました。

割引前の金額に対して割合で値引きをするので、割引前の金額が高いものからチケットを使用していきたいところです。問題には、「順番に購入する」とありますが、購入前のシミュレーションと考えれば購入順は気にしなくてよさそうです。

そのような要件に応えるようなアルゴリズムとしては、優先度付きキューがあります。優先度つきキューなら最大値の取り出しと値の追加が$O(\log N)$で完了します。

次に制約をみます。

品物の個数$N$と割引券の枚数$M$がともに最大$10^5$です。

割引券ごとに優先度つきキューから商品を取り出して割引を適用してキューに戻すという操作を繰り返しても、$O(M \log N)$で完了します。

以上から、以下の手順で実装します。

  1. 最大値を返す優先度付きキューを用意する
  2. 優先度付きキューに商品を追加する
  3. 割引券ごとに、優先度付きキューから商品を取り出して割引を適用してキューに戻す操作を繰り返す
  4. 優先度付きキューの値の合計を出力する

(defun solve ()
  (let* ((n (read))
         (m (read))
         (heapq (make-array 0 :fill-pointer 0 :adjustable t)))
    (loop repeat n
          for ai = (read)
          do (heap-push heapq ai))
    (loop repeat m
          for ai = (heap-pop heapq)
          do (heap-push heapq (floor ai 2)))
    (format t "~A~%" (reduce #'+ heapq))))


(defun shift-down (q idx)
  (let ((idx-l (+ (* idx 2) 1))
        (idx-r (+ (* idx 2) 2))
        (idx-next idx))
    (when (and (< idx-r (length q))
               (> (aref q idx-r) (aref q idx-next)))
      (setf idx-next idx-r))
    (when (and (< idx-l (length q))
               (> (aref q idx-l) (aref q idx-next)))
      (setf idx-next idx-l))
    (when (/= idx-next idx)
      (rotatef (aref q idx) (aref q idx-next))
      (shift-down q idx-next))))

(defun heap-push (q item)
  (vector-push-extend item q)
  (loop with idx = (1- (length q))
        for parent-idx = (floor (1- idx) 2)
        while (and (> idx 0)
                   (> (aref q idx) (aref q parent-idx)))
        do (rotatef (aref q idx) (aref q parent-idx))
           (setf idx (floor (1- idx) 2))))

(defun heap-pop (q)
  (cond ((zerop (length q))
         nil)
        ((= (length q) 1)
         (vector-pop q))
        (t
         (let ((ans (aref q 0)))
           (setf (aref q 0) (vector-pop q))
           (shift-down q 0)
           ans))))

(defun heap-top (q)
  (if (zerop (length q))
      nil
      (aref q 0)))

(solve)

Common Lispには優先度付きキューがないので、自前で実装しました。

AtCoder Beginner Contestを解いてみたリスト