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)))

2026年9月4日金曜日

40代文系社内SEがAtCoder Beginner Contest 143 D - Trianglesを解いてみた

様々な長さの棒が与えられ、その中から3本選んで三角形を作る選び方は何通りかを求める問題です。

まず注目したのは制約の10^3です。
あまり見かけない数字です。
N^2でも10^6なので2重ループが使えそうです。

3本の棒のうち、2本まで選ぶと3本目の長さの最大値が決まります。
棒の選び方にもともとの並び順は関係ないので、ソートしても問題ありません。
ということで2分探索が使えます。よっしゃ。

3本中2本を全探索で選び、残りの1本として選べる範囲を2分探索で求めることでACできました。
(defun solve ()
  (let* ((n (read))
         (l (make-array n :initial-contents (loop repeat n collect (read)))))
    (format t "~A~%" (solve2 l))))

(defun solve2 (l)
  (let* ((ll (make-array (1+ (length l)))))
    (loop for i from 0 below (length l) do (setf (aref ll i) (aref l i)))
    (setf (aref ll (1- (length ll))) (1+ (expt 10 3)))
    (sort ll #'<)
    (loop with ans = 0
          for i from 0 below (- (length ll) 2)
          do (loop for j from (1+ i) below (- (length ll) 1)
                   do (incf ans (solve1 ll i j)))
          finally
             (return ans))))

(defun solve1 (l i j)
  (labels ((f (min max)
             (if (= (1+ min) max)
                 min
                 (let* ((li (aref l i))
                        (lj (aref l j))
                        (mid (+ min (floor (- max min) 2)))
                        (lmid (aref l mid)))
                   (if (< lmid (+ li lj))
                       (f mid max)
                       (f min mid))))))
    (- (f j (1- (length l))) j)))

(solve)

2023年2月13日月曜日

AtCoderで入茶しました

 2023/02/04に開催された、Sky株式会社プログラミングコンテスト2023(AtCoder Beginner Contest 289)の結果レートが403になり、入茶できました。

自分も色変記事として思っていることをつらつらと並べてみたいと思います。

感謝について

家族の皆様、毎週コンテストに参加するために時間を調整してくれてありがとう。

AtCoder社様、毎週コンテストを開いてくださってありがとうございます。

Twitterでリアクションしてくださった皆様、ありがとうございます。一緒に盛り上げましょう。

自分について

新卒でシステムエンジニアとして就職して18年。社内SEに転職して5年。

今の環境に慣れてきて、若干飽きてきている部分もあったりして、少し変化が欲しかった。

参加したプロジェクト内ではプログラムをよく書けるほうだと思っていた。

AtCoderでも茶色くらいにはなれるんじゃない?

初めて参加したコンテストでは1完灰パフォ。

「AtCoderが保証できる実力はまったくありません。」うそやろ。

勉強について

まずは問題に慣れるために、過去問のAB問題を新しいものから順番に解いていった。

B問題まで安定してきたところで、茶色になるにはC問題まで解けないと難しそうだと感じた。

過去問の範囲をC問題まで広げ、C問題もほぼ解けるようになった。

D問題はまだほとんど解けない。競技プログラミングに出題されるアルゴリズムについての知識が不足している。

競プロ典型90問などで知識を増やし、類題を繰り返し解いて問題を解くための道具として使いこなせるようにしなければならない。

モチベーションを上げるために

自分よりちょっと強いユーザーをお気に入りに登録する。順位表をみて、「お、さすが」「今回は自分の勝ちですね」などニヤニヤしている。

TwitterでAtCoder関係のフォローを増やす。コンテスト開始前終了後にざわざわしているのが楽しい。

AtCoder Problemsでどれくらい問題を解いているかを確認する。一覧に緑色が増えると嬉しい。

モチベーションを下げないために

必死になりすぎない。

まだレートを上げたいとも思うし、勉強が必要だとも思う。

でも頑張らないとできないことは続かないので、頑張らなくてもできる範囲にする。

その代わりにあまりレートが上がらなくても受け入れる。

世の中には自分より熱心に取り組んでいる人がいるので、そういう人に追いつけなかったり、追い越されたりしても当たり前。気にしない。

あとは特に理由がないかぎり、毎週Ratedでコンテストに参加する。

競プロは仕事に役立つか

今までのところ、特に役立ったと感じる場面はない。

灰コーダーには灰コーダーなりの問題解決方法があり、それで十分なこともある。

「インデックスを貼れば検索が早くなるんでしょ」みたいなざっくりとした理解とか。

勉強を進めていく中で、見える景色が変わってくれると嬉しい。

最後に

まだ続けたいと思っています。

引き続きよろしくお願いします。

2021年5月24日月曜日

Strings.StrConvで全角に変換するときにサロゲートペア文字が文字化けする

Strings.StrConvで全角に変換しようとする文字列にサロゲートペア文字が含まれていると"??"に変換されてしまいます。

Debug.WriteLine("𠀋=" + Strings.StrConv("𠀋", VbStrConv.Wide)); // 𠀋=??と表示される

「.NET Frameworkは文字列を内部的にUnicodeしており、標準機能のStrConvを使用しているからヨシ!」と思っていると失敗するので注意が必要です。

参考

Strings.StrConv


2020年7月27日月曜日

PHPのビルトインWEBサーバーを使用する

PHPプログラムをブラウザから実行するにはWEBサーバーが必要です。
PHP5.4からビルトインWEBサーバーが組み込まれたため、開発時はPHPのみで動作確認ができます。
ビルトインWEBサーバーを起動するには、ドキュメントルートとするディレクトリをカレントディレクトリとし、php.exeに-Sオプションをつけて実行します。
C:\> cd path\to\docroot
C:\path\to\docroot> php -S 127.0.0.1:8080
もしくは-tオプションでドキュメントルートを指定します。
C:\> php -S 127.0.0.1:8080 -t C:\path\to\docroot

2020年5月18日月曜日

出力のバッファリングを使用した関数のエラー処理とLaravelのエラー処理との問題

弊社のシステムに下のようなプログラムがありました。

try {
    // ob_startなどは、file_get_contentsだけで処理を完結させるために使用。
    ob_start();
    $image = file_get_contents($path);
    $warning = ob_get_contents();
    ob_end_clean();
    if (strlen($warning) > 0) {
        throw new \Exception($waring);
    }
} catch (\Exception $e) {
    // エラー処理
}

コメントが何を伝えようとしているのか不明だったため、調べてみました。
PHPではエラーが発生すると、メッセージが画面に表示されます。
ob_start関数は出力のバッファリングを開始する関数で、エラーメッセージもバッファリングされます。
上のプログラムではfile_get_contents関数で発生したエラーのメッセージを一旦バッファリングしたあとで、例外としてthrowしたいようです。

しかし、弊社のシステムではフレームワークとしてLaravelを採用しており、エラーはErrorExceptionとしてthrowされるようになっています。
file_get_contentsでエラーが発生すると、ob_end_cleanが実行されないままcatchブロックが実行されてしまいます。

過去のPHPではある程度有効なパターンだったのかもしれませんが、Laravelには合わないので改修しなければなりません。