ラベル AtCoder の投稿を表示しています。 すべての投稿を表示
ラベル AtCoder の投稿を表示しています。 すべての投稿を表示

2026年9月23日水曜日

ABC138D - Kiを解いてみた

40代文系社内SEが、AtCoder Beginner Contest 138 D - Kiを解いてみました。

まず問題文を確認します。

根が頂点1の木があり、それぞれの頂点はカウンターをもっています。部分木のカウンターに値を加算する操作を繰り返し、最終的なカウンターの値を求めます。

まず、「木」と記載されていることからグラフの問題であることが考えられます。また、部分木に値を加算するとありますが、おそらくそのまま実装するとTLEになるだろうと思われます。

次に制約を確認します。

頂点の個数$N$が最大$2 \times 10 ^ 5$であり、操作回数$Q$も最大$2 \times 10 ^ 5$であることから、毎回頂点1に操作をするケースで計算量が$10 ^ {10}$を超えて間に合いません。

どのように計算量を抑えるかと考えたとき、根から部分木に値を加算していくという操作が累積和のようにみえました。すべての頂点が一直線につながっているようなケースを考えると、イメージしやすいかと思います。

累積和が使えれば、値を加算する操作は最後に1回だけ処理すればいいので、計算量を$2 \times 10 ^ 5$に抑えられそうです。

以上より、以下の手順で実装します。

  1. a、bを隣接リストとして読み込む
  2. カウンターを用意して、カウンターpにxを加算する
  3. 頂点1からグラフを探索し、累積和の要領で値を加算する

(defun solve ()
  (let* ((n (read))
         (q (read))
         (graph (make-array (1+ n) :initial-element nil))
         (counter (make-array (1+ n) :initial-element 0)))

    ;; a, bを読み込み、隣接リストとして保持
    (loop repeat (1- n)
          for ai = (read)
          for bi = (read)
          do (push bi (aref graph ai))
             (push ai (aref graph bi)))

    ;; p, xを読み込み、カウンターに記録
    (loop for i from 1 to q
          for p-i = (read)
          for xi  = (read)
          do (incf (aref counter p-i) xi))
    (let ((visited (make-array (1+ n) :initial-element nil)))
      ;; 頂点1を訪問済にして深さ優先探索を開始
      (setf (aref visited 1) t)
      (dfs graph counter '(1) visited)
      (format t "~{~A~^ ~}~%" (coerce (subseq counter 1) 'list)))))

(defun dfs (graph counter visiting visited)
  ;; 深さ優先探索でグラフを探索し、カウンターを更新する
  (when (not (null visiting))
    (loop with pos = (pop visiting)
          for dest in (aref graph pos)
          when (not (aref visited dest))
            do (incf (aref counter dest) (aref counter pos)) ; 次の頂点にカウンターの値を加算する
               (setf (aref visited dest) t)
               (push dest visiting))
    (dfs graph counter visiting visited)))

(solve)

ACできたのですが、訪問済のチェックを省略していて1回WAになってしまいました。

2026年9月14日月曜日

40代文系社内SEがAtCoder Beginner Contest 128 C - Switchesを解いてみた


まず問題文を読んでみる。
1行目と3行目は問題ないが、2行目がうまくのみこめない。

「電球iはki個のスイッチに繋がっており、スイッチsi1, si2, ..., sikiのうちonになっているスイッチの個数を2で割った余りがpiに等しいときに点灯します。」

ちょっとずつ分解してみる。
まず、piは制約に0または1と記載されているので、2で割った余りがpiに等しいとは、つまり偶数か奇数ということ。
「全部がonだったら」とか、「どれかひとつがonだったら」なら容易にイメージできるのに。

残りの前半部分については、電球ごとに繋がっているスイッチは異なり、
個数がkiで示され、続けてsijでどのスイッチに繋がっているかが示される。

続けて制約をみる。
NもMも10以下というのはかなり小さい値だ。
それ以外は特に気になることはなかった。

スイッチの状態がon/offの2種類であることと、個数が最大10個という制約から、スイッチの状態が全探索できそうだ。
$2^{10} = 1024$

電球が点灯しているかどうかを判定するには、電球ごとにonになっているスイッチを数えなければならない。
電球もスイッチも最大10個だから、最大でも10 * 10で網羅できる。

あとは実装方法だが、スイッチの状態がon/offの2種類なのでbit全探索が使える。
0以上(2^N)未満までループで回せば網羅できる。
例えば入力例1だと、0(00)、1(01)、2(10)、3(11)の4通りになる。

以上より、以下3ステップで問題を解く。
1. スイッチの状態ごとに、全ての電球が点灯しているか判定する
2. 電球ごとに、点灯しているか判定する
3. 電球に繋がっているスイッチごとに、onになっているか判定する


(let* ((n (read))
       (m (read))
       (k (make-array m))
       (s (make-array m :initial-element nil))
       (p (make-array m)))

  ;; k, sを読み込み
  (loop for i from 0 below m
        for ki = (read)
        do (setf (aref k i) ki)
           (loop repeat ki
                 for sij = (read)
                 do (push sij (aref s i))))

  ;; pを読み込み
  (loop for i from 0 below m
        for p-i = (read)
        do (setf (aref p i) p-i))

  ;; on/off
  (loop for stat from 0 below (expt 2 n)
        ;; 点灯している電球の個数
        for light-on = (loop for light from 0 below m
                             ;; onになっているスイッチの個数
                             for switch-on = (loop for switch in (aref s light)
                                                   count (logbitp (1- switch) stat))
                             count (= (mod switch-on 2)
                                      (aref p light)))
        count (= light-on m)
          into it
    finally
       (format t "~A~%" it)))