第12回 SQLコンテスト (2024/04/21)

第 12 回 SQL コンテストに参加しました。最終結果は 4 完 100 点で 11 位でした。

終結

近況報告

最近はデータベースを使って AOJ の提出検索をするアプリを作っているのですが、API でいつ提出データを取得するか (一括で取得するか、クエリが飛んできたタイミングで取得するか) の設計で悩んでいます。余談ですが、かっつくんが平日の日中に投げまくってるのを観測できて面白いです。

コンテストの振り返り

問題 1

データベースに新たに行を挿入する問題です。INSERT 句を使えばよいですが、列がたくさんあるのと追加行が 2 つあるので結構面倒です。

列名と値を 1 個ずつ写経して開始約 5 分後に AC。

INSERT
    INTO DEPARTMENT
    (
        DEPT_CODE,
        START_DATE,
        END_DATE,
        DEPT_NAME,
        LAYER,
        UP_DEPT_CODE,
        VALID_FLG,
        UPDATE_USER_NAME,
        USER_UPDATE_DATETIME
    )
    VALUES
    (
        '1100',
        '2024-04-01',
        NULL,
        '営業1課',
        2,
        '1000',
        '1',
        'TANAKA',
        '2024-04-01 00:00:00'
    ),
    (
        '2200',
        '2024-04-10',
        NULL,
        '製造2課',
        2,
        '2000',
        '1',
        'YAMADA',
        '2024-04-10 00:00:00'
    )

問題 2

文字列から空白を取り除き、さらにその先頭 10 文字を取得する問題です。前者は REPLACE 関数を、後者は SUBSTR 関数を使うことで実現できます。また、値が NULL のときの場合分けは CASE 句や IFNULL 関数を使うことでできます。

開始約 11 分後に AC。実装は問題 1 よりも軽かったです。

SELECT
    EMP_CODE AS CODE,
    EMP_LAST_NAME || EMP_FIRST_NAME AS NAME,
    EMP_ENG_NAME AS ENG_NAME,
    IFNULL(SUBSTR(REPLACE(EMP_ENG_NAME, ' ', ''), 1, 10), '未入力') AS CUTOUT_STR
FROM
    EMP
ORDER BY
    CUTOUT_STR DESC,
    CODE DESC

問題 3

2 つのテーブル ITEMITEM_HISTORY を比較し、どちらかのテーブルにだけある行や、両方にあるが変更されている行を検出する問題です。2 つのテーブルを ITEM_CODE をキーとして外部結合したいですが、SQLite では左外部結合しかできないので、ITEMITEM_HISTORY を左外部結合、ITEM_HISTORYITEM を左外部結合の 2 通りを試し、それぞれの結果を UNION 句で結合させればよいです。

開始約 26 分後に AC。ここもノーペナで乗り切ることができました。

WITH T1 AS(
    SELECT
        ITEM.ITEM_CODE AS CODE,
        ITEM.ITEM_NAME AS NAME,
        CASE
            WHEN ITEM_HISTORY.USER_UPDATE_DATETIME IS NULL THEN 'ADDED'
            WHEN ITEM_HISTORY.USER_UPDATE_DATETIME != ITEM.USER_UPDATE_DATETIME THEN 'UPDATED'
            ELSE 'UNCHANGED'
        END AS COMP_RSLT
    FROM
        ITEM
    LEFT OUTER JOIN
        ITEM_HISTORY ON ITEM.ITEM_CODE = ITEM_HISTORY.ITEM_CODE
    WHERE
        COMP_RSLT != 'UNCHANGED'
), T2 AS(
    SELECT
        ITEM_HISTORY.ITEM_CODE AS CODE,
        ITEM_HISTORY.ITEM_NAME AS NAME,
        CASE
            WHEN ITEM.USER_UPDATE_DATETIME IS NULL THEN 'DELETED'
            ELSE 'UNCHANGED'
        END AS COMP_RSLT
    FROM
        ITEM_HISTORY
    LEFT OUTER JOIN
        ITEM ON ITEM.ITEM_CODE = ITEM_HISTORY.ITEM_CODE
    WHERE
        COMP_RSLT != 'UNCHANGED'
), T3 AS(
    SELECT
        *
    FROM
        T1
    UNION ALL
    SELECT
        *
    FROM
        T2
    ORDER BY
        CODE DESC
)
SELECT
    *
FROM
    T3

問題 4

2024-03-01 から 2024-03-28 の 4 週間分のデータを曜日ごとに集計するという問題です。初動でカレンダーを調べた結果、2024-03-01 が水曜日であることがわかったので、まずは日付から曜日の数値を (日曜が 0、月曜が 1 というように) 算出して、曜日ごとに件数と売上の総和を計算しました。ここで問題となるのが件数が 0 の曜日も表示しなければならないという点ですが、0 から 6 の値のみからなる別のテーブルに左外部結合し、IFNULL 関数を使って NULL を 0 に置き換えるという方法を取りました。ここまで来れば後は表示フォーマットを整えるだけですが、売上額を 3 桁ずつカンマ区切りするのが思いの外面倒です。問題をよく見ると売上額が 10^9 未満であるという制約があるので、10^3 未満のとき、10^6 未満のとき、10^9 未満のときの 3 種類で場合分けをしました。各場合の表示方法については競技プログラミングで慣れていたので、時間が切迫する中でもミスなく書くことができました。

前半パートで文字列や小数を整数に変換する必要があるなど手間取ってしまったため最後は時間との勝負になりましたが、終了 2 分前に書き終えて一発 AC を取ることができました。

WITH T1 AS(
    SELECT
        (CAST(SUBSTR(SALES_DATE, 9, 2) AS INTEGER) + 4) % 7 AS TYPE,
        COUNT(*) AS CNT,
        SUM(SALES_AMT) AS AMT
    FROM
        SALES
    WHERE
        SALES_TYPE != 2
        AND UPDATED_NO IS NULL
        AND SALES_DATE BETWEEN '2024-03-01' AND '2024-03-28'
    GROUP BY
        TYPE
), T2 AS(
    SELECT 0 AS TYPE
    UNION ALL
    SELECT
        TYPE + 1 AS TYPE
    FROM
        T2
    WHERE
        TYPE < 6
), T3 AS(
    SELECT
        T2.TYPE,
        IFNULL(CAST(ROUND(CNT * 0.25, 0) AS INTEGER), 0) AS AVG_CNT,
        IFNULL(CAST(ROUND(AMT * 0.25, 0) AS INTEGER), 0) AS AVG_AMT
    FROM
        T2
    LEFT OUTER JOIN
        T1 ON T1.TYPE = T2.TYPE
), T4 AS(
    SELECT
        CASE TYPE
            WHEN 0 THEN ''
            WHEN 1 THEN ''
            WHEN 2 THEN ''
            WHEN 3 THEN ''
            WHEN 4 THEN ''
            WHEN 5 THEN ''
            WHEN 6 THEN ''
        END AS WEEK,
        AVG_CNT,
        CASE
            WHEN AVG_AMT < 1000 THEN AVG_AMT
            WHEN AVG_AMT < 1000000 THEN (AVG_AMT / 1000) || ',' || (AVG_AMT % 1000)
            WHEN AVG_AMT < 1000000000 THEN (AVG_AMT / 1000000) || ',' || ((AVG_AMT / 1000) % 1000) || ',' || (AVG_AMT % 1000)
        END || '' AS AVG_AMT
    FROM
        T3
    ORDER BY
        TYPE ASC
)
SELECT
    *
FROM
    T4

感想

今回もなんとか全完することができました。内容的には安定してきましたが、カラム名などの typo が目立つのでタイピングの速度と正確性が課題点だと感じています。

UTPC 2023 (2024/03/17)

UTPC 2023 に運営として参加したので、運営プロセスや各問題についての感想をまとめます。

スケジュール

10/15 - 1/20 原案作成・難易度評価
1/20 問題選定・作業者割当
1/20 - 2/18 writer 作業
2/18 - tester 作業
3/12 - 3/13 問題文校正
3/13 - 名札作成
3/17 コンテスト本番

運営陣

昨年の運営陣から今年は運営やれという要望を多数受けたので参加することにしました。この他にも \mathbb{X} での募集から新規参加したメンバーが多くいて、今年からの新規は 9 人と、昨年からの継続 7 人よりも多くなりました。他の大学コンテストを見ても運営 16 人というのは異様な多さだと思います。

作問体制

GitHub の issue に原案と想定解を書き、他のメンバーが解いての感想や難易度評価をスプレッドシートに書き込むという形式です。issue を作ったりリプライがあった場合に discord の通知が行くようになっていました。1 月までに 25 個程度の原案があったので、問題数不足に困ることはありませんでした。選定会では原案作成者の出題したい度合を元に 16 問を選定し、その後さらに 1 問を追加する形で 17 問になりました。

オンサイト

今年も MC Digital 様に会場をお借りさせていただきました。今回のオンサイト開催における反省点を以下に挙げます。

  • 開催数日前まで会場の撤収時間を正しく把握できていなかった。
  • マイクの電池状況を把握すべきだった。
  • 懇親時間が取れなかったのでコンテスト時間を前倒しにするなど調整すべきだった。
  • 解説会をできる場所が狭かった。可能ならポスター発表形式にしても面白いかも。
  • コンセントの使用状況を把握できていなかった。
  • 会場の集合場所が B1 であることを強調した周知ができていなかった。
  • AtCoder のコンテストページや connpass を公開できるのに誰もやらないという状態が続いてしまったため、公開できる状態になったら即座にするべきだった。

名札作成

AngrySadEight さん主導でアイコン入りの名札を作りました。デザインは AngrySadEight さんに作ってもらい、私含め数人で connpass のアイコン画像をはめ込む作業をしました。AngrySadEight さんに印刷とカットをしてもらい、当日私が購入した名札ケースに入れる作業をしました。

完成した名札

作問準備

各問題について writer・tester 1・tester 2 が割当てられます。writer の主な作業は問題文、テストケース、想定解の作成で、tester の主な作業は AC 解、想定嘘解法のチェックと入力が制約を満たしているのかの確認となります。

writer は基本的に原案作成者が行うことにしたため、原案を 1 個も出していない人と原案を複数出している人の間で負担がかなり偏重してしまったのは改善点です。しかし、仕事を多く振られた人も期限内にできそうになければ他の人に投げていればよりスムーズに準備ができていたと思います。仕事をやっていないことを指摘されてそれに逆ギレするというシチュエーションは最悪です。個人主義な人が多い弊学の悪い所だと思いました。

問題名

miscalc さん主導で、問題名の頭文字を問題番号に一致させるようにしました。原案の時点では問題名はバラバラだったのでかなり大変だったと思いますが、最終的にはかなり上手くまとまっていると思います。個人的には J: Japanese Gift Money が好きです。

問題文校正

開催週の火・水曜日の夜に、合計 8 時間のミーティングで全 17 問の問題文校正を行いました。セット全体での表記の統一、誤読する余地がないか、サンプルの説明など、各々が気になる点を列挙していってそれを 1 つずつ直していくという形で進めました。I, O 問題など原案時点の問題文が見る影もなく変わった問題もありましたが、元と比べると見違えるほど良くなったので時間をかけてやった意味は十分あったと思います。本番は clar 0 でした。表記ゆれの統一など今回で得られた知見は多くあるので、来年以降はよりスムーズに進められると思います。

当日

当日は運営 16 人のうち 15 人が現地に行きました。たくさん人がいたので、名札のケースへの収納や参加者の案内をスムーズに行うことができました。コンテスト中はオンサイト参加者の様子を見に行ったり、運営で FA 予想をしたり、順位表を眺めたり、食事に行ったりと楽しかったです。

各問題について

準備段階で全問題の AC を取ったので、それぞれについて簡単に感想をまとめます。括弧内は個人的な難易度評価 (AtCoder での配点) です。

A: Again Make UTPC (600)

AngrySadEight さん原案。UTPC 枠。UTPC が部分列として出てくる場合は 4 回以下ででき、答えが 4 になるケースも実際にあるという部分がかなり非自明。キャッチーな見た目とは裏腹に正しく詰めるのが難しく、ペナ祭りと化していた。

B: Black or White 2 (600)

Mitsubachi さん原案。構築その 1。実は自明な最適値を達成できる。やりようは色々あって誰にでも解けうる問題で、個人的には A 問題よりも取り組みやすいと思っていた。しかし結果的にはそれほど AC されなかった。

C: Contour Multiplication (700)

SSRS さん原案。SSRS その 1。原案当初は乗算ではなく加算だったため、私が XOR convolution N 回で解けることを指摘したらそれを阻止するために任意 mod での乗算になった。SSRS さん曰く典型とのことだが、私は全然わからなかった。ためになる一問。

D: DRD String (600)

confeito さん原案。中難易度数え上げその 1。DRD 文字列を DRD として表す方法として何があるかを考えると自然に考察が進む。個人的には C 問題より幾分か取り組みやすいと思っていたが、実際の結果を見てもそのようだった。ちなみに元ネタはダンスロボットダンスというボカロ曲らしい。

E: Equally Dividing (500)

Mitsubachi さん原案。構築その 2。B 問題と同じく、自明な必要条件を満たしているものは全て作ることができる。実際に手を動かして考えやすいので、B 問題と比べて取り組みやすいと感じていたが、実際は予想より大きな差が出た。

F: Flip or Not (1100)

Mitarushi さん原案。自力では初手の多項式で剰余を取るパートから気づかなかった。その後も正しく立式して \mathbb{F}_2 多項式の諸演算を実装する必要があるので、解説を見た後に通すのですらかなり苦労した。個人的には 0 AC 筆頭だと思っていたが、本番では hos_lyric さんに解かれていて感嘆した。

G: Graph Weighting (800)

tokusakurai 原案。グラフと最適化。原案当初は W 固定かつ最適化パートはなしの構築問題だったが、運営陣から簡単という意見を多数受けたので強化をすることとなった。数え上げにする案もあったが、のいみの案をさらに強化をする形で現在の問題となった。ステップが 3 つ程あって、どれも突飛なものではないため難易度推定は難しかったが、本番は 6 AC と想定より多くの人に解いてもらえた。

H: Huge Segment Tree (700)

SSRS さん原案。SSRS その 2。中難易度数え上げその 2。問題設定が面白い。想定解は多項式で立式して式変形を重ねているが、分割された区間の一番長い部分がどの段にあるかで場合分けして立式しても畳み込みの形になって解ける。当日運営の控室で SSRS さんに H は fps 経由しなくても解けるという話をしたら、fps ではなく多項式だと説教された。

I: I Love Marathon Contest (1200+)

noimi さん原案。全完阻止枠。高難易度数え上げその 1。AGC-E,F レベルくらいあると思っている。周回数が累積和の \max - \min がその +1 になることに気づくのは難くないが、+1 される必要十分条件がかなり難しい。そこから高速に計算できる形にするのもかなり難しい。そして、解説を書くのも難しい。間違った OEIS エスパーができるようにサンプルは N\leq 5 までにしてもらったが、結局提出すら 1  回もされなかったのは残念。

J: Japanese Gift Money (500)

miscalc さん原案。簡単枠その 1。日本のお祝儀が題材ということで、命名がとても上手い。問題自体は、最初に考えそうな予想が正しいでセットの中で 2,3 番目くらいに簡単だと思ったが、実際には想定よりは解かれていなかった。

K: Kth Sum (800)

SSRS さん原案。SSRS その 3。想定解のブロック分けは自然な着想で、これで計算量が落ちているのが面白い。その他にも色々な解法がありそうなため、セットの高難易度帯の中では比較的正解しやすい問題だと思っていた。実際、本番でも 10 チーム以上が正当していて、中には \Theta(N^2) の高速化で通したチームもあるようだった。制約強化や TL 調整をする余裕がなかったのが残念。

L: Largest Triangle (400)

confeito さん原案。簡単枠その 2。このような問題では何かを固定して考えてみたくなるが、今回は最大辺を固定することで上手くいく。出力形式に関しては小数にするか平方根の中身にするかといった議論があった。

M: Majority and Permutation (800)

chinerist さん原案。高難易度数え上げその 2。原案時は、順列 P が与えられたときに正しい括弧列であって P に従って並び替えても正しい括弧列となるようなものの構築問題だった。これに対して私が優先度付きキューを用いた貪欲法を指摘したところ、それでは面白くないということで数え上げ問題に転生した。当初は制約は N\leq 50 だったが、私が P についての必要十分条件を指摘した結果、制約が N\leq 10^5 まで上がった。upsolve おすすめの問題。

N: Number of Abbreviations (500)

SSRS さん原案。SSRS その 4。簡単枠その 3。問題設定、解法共にとてもシンプルで面白い問題。それと同時に、詰まる人はとことん詰まってしまいそうという印象がある。私も筋のいい着想に至るまで 10 分程要した。部分文字列数え上げをする嘘が多く出ていたのは意外だった。

O: Optimal Train Operation (800)

confeito さん原案。5h 枠。解法としては挿入・削除する CHT を考えたくなるが、実は履歴を管理するシミュレーションだと最悪ケースで \Theta(N^2) になってしまうため、挿入・削除の履歴を木構造として持って HLD を使うというのが原案の想定解。こちらでも通すことはできるが、実装のみならず TL や ML も厳しい。chinerist さんのテスター解である分割統治を使うと、計算量も実装もかなり楽になる。方針が重要な問題。

P: Priority Queue 3 (1000)

chinerist さん原案。高難易度数え上げその 3。M 問題の制約強化前の想定解の部分問題として作られた問題で、ad-hoc 要素で面白い問題だったので追加で採用してもらった。chinerist さんの最初の想定解はいわゆる箱根駅伝 dp を使うものだったが、それとは独立に私が公式解説の解法を思いつき、こちらの方がやや計算量が良かったためメインの解法になった。こちらも upsolve おすすめの問題。

Q: Quotiet Sum (600)

tokusakurai 原案。この位置には元々 SSRS さん原案の幾何の問題があったが、前週の ABC-G 問題で似たような問題が出題され、解法の本質パートが \mathbb{X} で投稿されてしまったため出題を見送りとなった。Quotient Sum は補填の簡単枠として私が即興で作った問題だったが、ちょうど良さそうということで採用となった。codeforces Div.1-A くらいの難易度のつもりだったが、実際は意外と捉え所が難しいようだった。

おまけ

I 問題のタイトル

続き

I hate Marathon Contest but I like MCD! が面白い。

Q 問題の爆破

Q がそのままだったらだいぶ難易度やばかったかもしれない。

セット全体の難易度調整

最終的な結果を見てもだいぶ難しいセットだったと思いますが、簡単枠が嘘解法を誘発するような問題ばかりだったのが全体としての AC の低さに大きく寄与しているのだと思います。Universal Cup でもトップのチームにもギリギリ全完されないくらいの難易度だったのは良いですが、多くのチームにとって高難易度の面白い問題に割ける時間が短くなってしまったのは調整ミス感があります。全体的にも WA が発生しやすい問題が多かったので、その辺りをふまえた難易度調整は今後の課題になると思います。5h という枠に囚われず、より長いコンテスト時間や Day1 Day2 という方式にするのもありですね。

感想

個人としては作問活動をするのは初めてで、その中で 2 問出題できたのは非常に良い経験となったので、今後も作問活動を行っていきたいです。コンテストとしては問題名や問題文など細部までこだわり、clar も 0 と非常に質の高いものができたと思っています。会場をお貸しいただいた MC Digital 様、コンテストサイトを提供いただいた AtCoder 様、ご参加いただいたコンテスタントの皆様にこの場を借りて感謝申し上げます。

長野電鉄 (2024/02/29)

北陸新幹線長野電鉄を利用して、長野と湯田中に行ったのでその記録を書こうと思います。

長野電鉄

長野電鉄長野線は、長野県長野市にある長野駅 (A) と長野県下高井郡山ノ内町にある湯田中駅 (B) を結ぶ、全長 33.2 km の路線です。長野電鉄第三セクターではない私鉄で、現在は長野線しか残っていませんが、かつては長野線から分岐して屋代線や河東線で鉄道が運行されていました。長野線は全線電化されており、日中は毎時一本程度の特急列車も運行されています。運賃は長野から湯田中まで片道 1,190 円と高めに設定されていますが、特急料金は +100 円なので、特急が乗り得となっています。

出典:国土地理院ウェブサイト

長野までの行き方

当然ですが、長野電鉄に乗るためにはまず長野駅に行く必要があります。長野市は長野県の中でもかなり北部に位置しているため、東京から在来線でアクセスするにはそこそこ時間がかかり、往復だけで一日の大半が終わってしまいます。私は中学時代から 18 きっぷユーザーでしたが、そのアクセスしづらさ故これまでは長野電鉄に乗車したことはありませんでした。

今回は、旅せよ平日!JR東日本たびキュン♥早割パスという JR 東日本の期間限定のお得なきっぷを使いました。この切符は 1 万円で新幹線含む JR 東日本及び第三セクター路線が乗り放題になるので、新幹線で長野方面に行くことに決めました。

乗車記

朝 5 時半に起床し、少し食事をしてから東京駅に向かう。東京駅には 7 時頃に到着。乗車するのは 7 時 20 分発の北陸新幹線かがやき 503 号金沢行。北陸新幹線の始発は 6 時 16 分発のかがやき 501 号であるが、起床に自信がなかったので一本遅らせた。かがやき号は全車指定席なので指定席乗車券を購入する必要があるが、今回のきっぷで 2 回まで無料で発券できる指定席券を利用できた。同日の東北新幹線はやぶさ号指定席は半月程前には既に満席になっていたが、かがやき号は前日でも余裕で 3 人席を独占することができた。

乗車した車両は JR 西日本の W7 系。JR 西日本車両にのみ搭載されている車内チャイム「北陸ロマン」がかなり好きなので、気分が高揚した。座席に着くと、備え付けの JR 東日本車内サービス誌「トランヴェール」と北陸新幹線敦賀開業が特集されている雑誌を読んだ。私が中高時代のトランヴェールは今のものよりも大きく、巻末に JR 東日本の路線図が載ってていたのだが、それがなくなっていたのは残念である。今年 3 月の北陸新幹線延長開業はホットな話題であるが、かがやき号が金沢以遠で各駅停車になるというのには腰を抜かしてしまった。新幹線の最優等種別が県庁所在地駅を容赦なく通過していくのは個人的には好みなのだが、途中から失速してしまうのは悲しい。

JR 西日本の W7 系。上越妙高駅より先は JR 西日本の管轄となる

大宮駅までは最高 130 km/h なので、在来線と同程度の時間を要する。しかし、ここからがかがやき号の真骨頂で、大宮を出ると次の長野まで約 200 km をノンストップで走り抜ける。とりわけ上越新幹線と分岐する高崎駅までは最高 275 km/h で走るので疾走感を感じられる。高崎駅を出た後は最高 260 km/h となり、トンネル区間も多いため速度はやや控えめとなる。実際地図を見ると、この区間で平地を走るのは高崎・軽井沢・佐久平・上田・長野駅近辺のみであることが確認できる。それでも在来線だと半日かかる大宮長野間を 55 分というのは驚異的なスピードである。

8 時 40 分に長野駅に到着。小腹が減っていたため、駅ビルでりんごジュースとおやきを買ってから、まずは善光寺へ赴いた。長野駅を出て程ない所に善光寺通りがあり、沿って歩いていけば善光寺に到達する。善光寺まではなだらかな上り坂になっていて、この通りを歩くだけでも雰囲気を感じることができる。

30 分ほど歩いて善光寺入口に到着。ここに来るのは初めてではないが、以前来たときは 1 時間弱しかなかったため、じっくりと見られるのは今回が初めてである。三堂、資料館、お戒壇巡りがセットになっている参拝券 (1,200 円) を購入し、まずは本堂の中へ入った。

善光寺本堂

本堂は高さ、横幅、奥行きどれを取っても非常に広い印象を受けた。薄暗い堂内に広がる一面の畳に本尊が佇んでいるのが特徴的である。最初にお参りをしてからは、本堂奥でお戒壇巡りをした。お戒壇巡りは、本堂地下を一切の光がない真っ暗闇の中で周回し、本尊の真下にある「開運の錠前」に触れることでご利益を得られるというものである。暗闇の中を進むために、常に右手で壁を触りながら伝っていくのだが、木造の壁は冷え切っており、手を悴ませるか暗闇で迷子になるかの厳しい二択を突き付けられた。前を行っていた数人のグループがはしゃいでいたので特段怖さを感じることはなかったが、やはり出口で光明が差したときの安心感は底知れなかった。出口は入口のすぐ隣で、途中何回か右折して地下をぐるっと一周回るという構造になっていたようだ。非常に興味深い体験となった。

本堂を一通り見終わった後は、すぐ横にある経蔵へと足を運んだ。経蔵は文字通り経典を所蔵している回転式の書庫であり、一周回すことでお経を読誦するのと同じ功徳があるとされている。私も実際に回してみたが、回し始めはかなりの力が必要で経典の重みを体で感じることができた。経蔵を後にすると、今度はタワーらしき建物があったのでそちらへ向かう。

善光寺資料館。遠目では先端部分のみが見えるので、よくわからない

このタワーらしき建物は三重塔のような構造をしている資料館だった。この塔は、戊辰戦争から第二次世界大戦までの間に戦死した英霊を祀る日本忠霊殿であり、そこに資料館が併設されているようだった。地下の資料館では、善光寺本尊の阿弥陀如来が死者を甦らせたなどの伝説が 10 個くらい書かれているコーナーが特に印象に残っている。

最後は、参拝券で登ることのできる山門に登った。城や門に特有の非常に急な階段を上った先には回廊があり、そこから本堂や長野駅方面の善行寺通りを一望することができる。しっかりと時間を使って善光寺を見るのは初めてだったが、アトラクション的な要素も多々あって十分に満喫することができた。

山門から望む善行寺通り

善光寺を出てからは、本題の長野電鉄に乗るために最寄り駅の善光寺下駅まで、列車の出発が間近だったので駆け足で急いだ。長野電鉄湯田中まで往復するなら 1 日フリーきっぷの方が安いという断片的な記憶があったので、値段を確認せずに窓口でフリーきっぷを申し込む。フリーきっぷは 2,070 円で、単純な往復よりも 310 円安くさらに特急も乗り放題になるので実際にお得になっていた。スタンプの押印に少し時間がかかっていたようで、ホームに着く頃には既に列車が入線していた。列車は引退した東急の 8500 系で、私が幼い頃に地元を走っていた電車との邂逅に思わず笑みがこぼれた。

かつて東急の主力車両だった 8500 系

11 時 20 分に善光寺駅を出発。直射日光と暖房で暑いくらいの車内で一面に広がる雪景色を眺めながら列車は 11 時 41 分に終点の須坂駅に到着する。親戚が富士通須坂工場 に勤務していたことがある影響で何度か話に聞いたことのある地だが、実際に来るのは初めてだ。須坂には長野電鉄の車庫があり、東京メトロ (営団地下鉄) 日比谷線の 03 系や、小田急ロマンスカーの 10000 形 HiSE といった、かつて首都圏を走っていたレジェンドが勢揃いしていて、しばし懐古に浸った。

ここから湯田中駅までは、ロマンスカーを利用する。11 時 50 分に須坂駅を発った 4 両編成の特急ゆけむり号は最初はほぼ満席だった。途中の小布施駅信州中野駅でもある程度乗客を降ろした後、12 時 18 分に終点湯田中駅に到着。

小田急 10000 形。ロマンスカー

湯田中で何をするかは特に考えていなかったが、猿が有名であることは知っていたので、ひとまず地獄谷野猿公苑に行くことにした。湯田中駅前からバスに 10 分程乗車し、スノーモンキーパーク停留所まで移動する。ここから野猿公苑まではまだ 2 km 程あるのだが、山道になっている関係でバスはここまでしか来れないようだ。

時刻は 13 時前になっていたので、まずはバス停近くのそば処で食事をすることにした。私はそばとうどんでは断然うどん派なのだが、せっかく長野に来たので十割蕎麦を注文。濃厚な蕎麦もさることながら、山菜の天ぷらや山葵も新鮮でとても美味しかった。観光地価格ではあったが、〆の蕎麦湯もあって満足の食事だった。

長野の十割蕎麦。蕎麦湯もついているのが嬉しい

食後は直接野猿公苑へと向かった。ここからは舗装されていない山道で、転落が死を意味するような厳しさがある。木の枝に積もった雪が突然降ってきたり、斜面の雪が崩れてきたりすることもしばしばあり、スリルがあって非常に面白い。

雪化粧した山道を行く

多数の観光客とすれ違ったりしながら 30 分程山道を歩くと地獄谷野猿公苑に到着する。入苑料は 800 円。苑内は主に外国人観光客で溢れかえっており、驚きを隠せなかった。ニホンザルは外国人にウケのいいのだろうか。

onsen for human

苑に入る際に猿に近づかないように注意を勧告されたが、こちらから近づこうとしなくても足元には猿がいるというような状況になっていた。寒さゆえ 2,3 匹の猿が身を寄せ合って毛づくろいをしているのが可愛らしかった。最深部まで行くと、猿が入る温泉がある。ライブ中継カメラもあり、どうやらここが一番の見どころとなっているようだ。私がいたときには実際に温泉に浸かっている猿を見ることはできなかったが、脇で温かそうにしているのを見て少し羨ましくなってしまった。

onsen for monkey

公苑内には人間用の温泉もあったが、そこはかなり古いうえに完全に観光地価格だったので、一旦バス停の方まで引き返すことにした。山を下った後は次のバスが一時間半後だったので、ひとまず湯田中駅に向かって足を進める。歩いている途中で日帰り入浴可能な温泉施設「みやま温泉わくわくの湯」があったので、衝動的に立ち寄った。料金は 500 円で受付は常駐しておらず、硬貨を投入するとゲートが通れるようになるというシステムになっていた。浴場への入口を開けるとまず体の洗い場があり、そのさらに先の入口を開けると露天風呂がある。先客はいなかったので、5 m 四方以上もの広々とした露天風呂を占有できた。屋根に積もった雪がぱらぱらと降ってくるのも風情があり、気分が良かったので随分と長居をしてしまった。

30 分程して上がると、またもや外国人観光客が続々と訪れていた。人が少ない時間帯に来ることができたのは幸運だったようだ。脱衣所には鍵付きのロッカーと無料のドライヤーもあり、500 円とは思えない程の質の高い温泉施設だった。湯上り処で朝に買ったりんごジュースを飲むと、バスの発車時刻まで約 20 分となっていたので、結局最寄りのバス停まで引き返してバスで湯田中駅に帰ることにした。

17 時 10 分頃に湯田中駅に到着。出発間近の 17 時 15 分発普通信州中野行に乗車する。車両は東京メトロ日比谷線の 03 系で、東横線と直通運転していた時代にはよく乗った思い出がある。疲れが溜まっていたのですぐ寝てしまい、気づいたときには終点の信州中野駅に到着していた。

営団地下鉄 03 系。顔は丸ノ内線 02 系っぽい

信州中野駅には 17 時 35 分に到着し、17 時 48 分の当駅始発普通長野行に乗り継ぐ。将棋の A 級順位戦最終局を見たりしながらぼーっとしてたらいつの間にか終点の長野駅に到着。時刻は 18 時 37 分で、ホームは帰宅するサラリーマンでごった返していた。

元々の想定では長野からさらに新潟方面へ北を目指そうと考えていたが、善光寺参りや地獄谷野猿公苑で思いの外時間がかかったことですっかり夜になってしまったため、少々もったいないが大人しく東京に帰ることにした。帰りの新幹線の候補は 19:05 かがやき号、19:41 はくたか号、20:29 かがやき号の 3 択。全車指定のかがやき号は少々空きがあり、はくたか号の指定席は 3 人座席の真ん中以外空いていないという状況だった。

いい感じの指定席がなかったのでとりあえず食事をすることにした。昼にそばを食べたので特に行きたいところはなかったので、善光寺通り近くのラーメン屋に入店。海なし県の海鮮出汁ラーメンに興味を駆られたので注文する。こちらは今まで食べたことがないくらいの濃厚なスープだった。

魚介ラーメン@麺匠佐蔵

提供が早かったため 19 時 20 分頃には店を出ることができたので、41 分発のはくたか号乗車を目指す。指定席はほとんど埋まっていたので、全体の 1/3 を占める自由席に賭けることにした。はくたか号利用者は長野以南が多そうというのと、最悪立ちになってもいいという見立てである。19 時 41 分発はくたか 574 号東京行に乗車。先頭 1 号車の乗車率は 3 割程度で難なく着席できた。かがやき号が通過する上田、佐久平、軽井沢、高崎に停車するので往路と比べてややテンポが落ちている印象を受けたが、当然ながらそれでも速いことに変わりはない。ここでも A 級順位戦中継を見たりしながら東京まで帰った。東京駅には 21 時 16 分に到着。もう少し新幹線乗り放題を活かしたかったという気持ちもあったが、結果的には過不足のない適度な旅行になったと思う。

感想

新幹線乗り放題系の企画でどこにいくかは難しい...

第11回 SQLコンテスト (2024/02/19)

第 11 回 SQL コンテストに参加しました。最終結果は 4 完 100 点で 6 位でした。

終結

近況報告

例によって SQL を書くのはほぼ 2 ヶ月振りになります。前回からの進歩としては SQL の本を一冊概ね読んだので、ウィンドウ関数などの文法や SQL の思想についての理解が少し上がったという点があります。

コンテストの振り返り

問題 1

1 問目から表示ではなく削除を要求される問題で意表を突かれました。基本的な DELETE 文を使えるかが問われている問題ですが、日付範囲を 2024-01-01 から 2024-01-03 までと読み違えて 1 回 WA を出してしまいました。

開始約 3 分後に AC。幸先の悪いスタートとなりました。

DELETE
FROM
    STOCK
WHERE
    LAST_DELIVERY_DATE BETWEEN '2023-01-01' AND '2023-01-31'
    AND ACTUAL_AMT = 0

問題 2

こちらはメールアドレスの '@' 以降から '.co.jp' を除いた部分を表示することに主眼を置いた問題です。SUBSTR 関数を使って部分文字列を抽出したいですが、'@' が何番目にあるかがわからないと使えません。調べてみると、INSTR 関数で部分文字列の位置を検索できることがわかりました。具体的には、文字列 S,T について INSTR(S,T)S[i:i+|T|] = T となる最小の i(存在しなければ 0)を出力します。今回の場合は INSTR(EMAIL, '@') で '@' の index を取得できます。'@' がもし複数あったら面倒なことになっていましたが、出してみると無事に通って安心しました。

開始約 12 分後に AC。

SELECT
    MEMBER_CODE AS MEMBER,
    EMAIL AS EMAIL,
    SUBSTR(EMAIL, INSTR(EMAIL, '@') + 1, LENGTH(EMAIL) - INSTR(EMAIL, '@') - 6) AS DOMAIN
FROM
    MEMBER_MST
ORDER BY
    DOMAIN DESC,
    MEMBER DESC

問題 3

各月の総売上を計算し、直近 12 ヶ月の総和と年内の総和を計算する問題です。累積和の問題なので、ウィンドウ関数が効力を発揮します。まず 2023 年以外のデータも残したまま直近 12 ヶ月の和を SUM(MONTH_AMT) OVER(ORDER BY YEAR_MONTH ASC ROWS BETWEEN 11 PRECEDING AND 0 PRECEDING) で計算し、その後 2023 年のデータのみを残して今度は SUM(MONTH_AMT) OVER(ORDER BY YEAR_MONTH ASC) でシンプルに累積和を計算します。

サクッと書けて開始約 26 分後に AC。私が先月書いたウィンドウ関数の記事が参考になりました。

WITH T1 AS(
    SELECT
        SUBSTR(SALES_DATE, 1, 7) AS YEAR_MONTH,
        SUM(SALES_AMT) AS MONTH_AMT
    FROM
        SALES
    WHERE
        UPDATED_NO IS NULL
    GROUP BY
        SUBSTR(SALES_DATE, 1, 7)
), T2 AS(
    SELECT
        YEAR_MONTH,
        MONTH_AMT,
        SUM(MONTH_AMT) OVER(
            ORDER BY YEAR_MONTH ASC
            ROWS BETWEEN 11 PRECEDING AND 0 PRECEDING
        ) AS YEAR_MOVE_AMT
    FROM
        T1
), T3 AS(
    SELECT
        *
        FROM T2
    WHERE
        YEAR_MONTH BETWEEN '2023-01' AND '2023-12'
), T4 AS(
    SELECT
        YEAR_MONTH,
        MONTH_AMT,
        SUM(MONTH_AMT) OVER(
            ORDER BY YEAR_MONTH ASC
        ) AS CUML_AMT,
        YEAR_MOVE_AMT
    FROM T3
    ORDER BY YEAR_MONTH
)
SELECT
    *
    FROM T4    WITH T1 AS(
    SELECT
        SUBSTR(SALES_DATE, 1, 7) AS YEAR_MONTH,
        SUM(SALES_AMT) AS MONTH_AMT
    FROM
        SALES
    WHERE
        UPDATED_NO IS NULL
    GROUP BY
        SUBSTR(SALES_DATE, 1, 7)
), T2 AS(
    SELECT
        YEAR_MONTH,
        MONTH_AMT,
        SUM(MONTH_AMT) OVER(
            ORDER BY YEAR_MONTH ASC
            ROWS BETWEEN 11 PRECEDING AND 0 PRECEDING
        ) AS YEAR_MOVE_AMT
    FROM
        T1
), T3 AS(
    SELECT
        *
        FROM T2
    WHERE
        YEAR_MONTH BETWEEN '2023-01' AND '2023-12'
), T4 AS(
    SELECT
        YEAR_MONTH,
        MONTH_AMT,
        SUM(MONTH_AMT) OVER(
            ORDER BY YEAR_MONTH ASC
        ) AS CUML_AMT,
        YEAR_MOVE_AMT
    FROM T3
    ORDER BY YEAR_MONTH
)
SELECT
    *
    FROM T4

問題 4

ここまで特にエラーに苦しむこともなく、実際 3 問目までのスピードは過去最速だったので全完のチャンスだと感じていました。問題文は長くてかなりわかりにくいですが、全ての商品の組 (A,B) について

  • A,B を両方買った人数
  • A を買った人数
  • B を買った人数
  • 全体の人数

がわかれば求めたい値を全て計算できます。まず商品の組を列挙しなければいけませんが、これには交差結合 (cross join) が使えます。さらに、交差結合した後に 2 つの SESSION_ID が一致する行のみを残し、DISTINCT 文を使うことで A,B を両方買った人数を求めることができます。同じく DISTINCT 文を使うことで、各商品を買った人数、全体の人数を求めることもできるので、これらの結果を内部結合 (inner join)、外部結合 (outer join) を駆使して商品の組の表に適切に組み込むことで、欲しかった 4 つの値を全てまとめることができます。

実装は少し長くなりましたが開始約 52 分後に AC。余裕を持って正解することができました。

WITH T1 AS(
    SELECT
        COUNT(DISTINCT SESSION_ID) AS CNT_ALL
    FROM
        PURCHASE_HISTORY
), T2 AS(
    SELECT
        *,
        COUNT(DISTINCT SESSION_ID) AS CNT_A
    FROM
        PURCHASE_HISTORY
    GROUP BY
        ITEM_CODE
), T3 AS(
    SELECT
        P1.ITEM_CODE AS ITEM_A,
        P2.ITEM_CODE AS ITEM_B,
        COUNT(*) AS CNT_AB
    FROM
        PURCHASE_HISTORY AS P1
    CROSS JOIN
        PURCHASE_HISTORY AS P2
    WHERE
        P1.SESSION_ID = P2.SESSION_ID
    GROUP BY
        P1.ITEM_CODE,
        P2.ITEM_CODE
), T4 AS(
    SELECT
        ITEM_A,
        ITEM_B,
        CNT_AB,
        TA.CNT_A AS CNT_A,
        TB.CNT_A AS CNT_B
    FROM
        T3
    INNER JOIN
        T2 AS TA ON ITEM_A = TA.ITEM_CODE,
        T2 AS TB ON ITEM_B = TB.ITEM_CODE
    WHERE
        ITEM_A != ITEM_B
), T5 AS(
    SELECT
        *
    FROM T4
    CROSS JOIN
        T1
), T6 AS(
    SELECT
        ITEM_A,
        ITEM_B,
        ROUND(100.0 * CNT_AB / CNT_ALL, 5) AS SUPPORT,
        ROUND(100.0 * CNT_AB / CNT_A, 5) AS CONFIDENCE,
        ROUND(1.0 * CNT_AB * CNT_ALL / (1.0 * CNT_A * CNT_B), 5) AS LIFT
    FROM
        T5
    ORDER BY
        LIFT DESC,
        SUPPORT DESC,
        CONFIDENCE DESC,
        ITEM_A DESC
)
SELECT
    *
    FROM T6

感想

初全完でかなり嬉しいです。次もまた頑張ります。

SCIS 2024 (2024/01/23-26)

2024 年 1 月 23 日(火)から 26 日(金)にかけて、長崎県長崎市の出島メッセ長崎にて開催された SCIS 2024(暗号と情報セキュリティシンポジウム)に参加したので、その記録を書こうと思います。

SCIS とは

SCIS は電子情報通信学会主催の国内会議で、Symposium on Cryptography and Information Security の略称です。暗号系の学会としては国内最大規模で、4 日間に渡って同時に 6,7 個のブースで平行して発表が行われます。発表形式はスライドをプロジェクターに投影する方式で、1 件あたり発表 15 分・質疑応答 5 分の合計 20 分となっています。内容としては耐量子計算機暗号を始めする暗号理論から現実世界のセキュリティ実装まで、多岐に及んでいます。

参加経緯

今回は、発表ではなく昨年に同学会主催の研究会で発表した論文(卒論での研究)の表彰式出席という名目で参加しました。11 月頃に急にメールが来て SCIS で表彰式を行うと言われてびっくりしましたが、当然学会への参加も必要でその他の旅費などもかかってしまうため、現地に行くのは無理かなと思っていました。ダメ元で元指導教員に聞いてみたところ、すんなり行っていいと言われて拍子抜けしました。昨年も研究室に同様の状況の人がいて、その人は表彰式に出た後日帰りで即帰らなければいけなかったようなのですが、なぜか私は全日参加を許されました。

大会前日まで

当初は前泊する気満々で 22 日から 26 日まで行く予定で考えていて、最初にホテルを予約しました。恐らく同学会に参加する人が多い影響でホテルはこの時点で既に安いところは埋まっていて、結局 4 泊以上の割引がついているホテルを予約しました。4 泊の朝食ありで合計約 32,000 円です。年末頃に先生から連絡が来て、23 日の朝に移動しろと言われました。本当はサンライズやら長距離フェリーやら使いたい経路はいくつもあったのですが、面倒かける訳にもいかないので素直に従うことにしました。結局ホテルは 3 泊で取り直しとなり、どこも日 1 万円程度はかかりそうだったので大浴場のあるドーミーインに切り替えました。飛行機はできるだけ安くしろと繰り返し言われたので片道 1 万円足らずで行ける LCC を予約しましたが、後出しで ANAJAL にしろと言われ、血の気が引きました。キャンセル出来なかったので結局取り直す必要はありませんでしたが、それなら最初から JALANA を使えば良かったなという感はあります。そんなこんなで準備は済んだので一安心していましたが、新年早々羽田で JAL の事故があって不安を煽られました。もちろんこれは杞憂だったのですが。

参加記

大会 0 日目 (1/22 月)

前日は奇しくも暗号系の授業の期末試験があった。試験に出ていたら前日入りは難しかったので、結果当日入りで良かったという面もある。授業には初回以降一切出ておらず、前週に少し勉強したものの単位が来れば上々という感じだった。そうは言ってもこれを取れれば大学院の卒業単位が足りるのでなんとか取っておきたいところ。

試験では定義わかってればやるだけみたいな枠がほとんどなく、3 つある大問のうち 1 つは題意すらわからないという惨状だったが、なんとかわかりそうな大問に全振りしたら一応全問解け、単位は来そうという感触はあった。試験が終わった後は企業との面談があったため即帰宅。面談はなぜか過去一盛り上がり、就活系の面接・面談の中では一番楽しかった。

用が済んだ頃にはすっかり暗くなっていたので荷造りを始める。LCC の手荷物重量制限 7 kg を越えてしまうと追加で 4 千円も取られるので、重量検査にはかなりナーバスになっていた。実際 4 日間の着替えと鞄の重量だけで 6 kg を超過し、PC の持参は泣く泣く断念。翌朝は早いので 22 時頃に就寝した。

大会 1 日目 (1/23 火)

成田 9 時発の飛行機に間に合わせるため、余裕を持って 5 時に起床。駅で電車を待っている間、生活習慣が崩壊している tabr 氏から昨日のテストが 36 点だったと言われ、自分も確認してみると、まさかの 61 点(単位確定演出)。高々半分の問題にしか手を付けていなかったがが、完答した大問にボーナスが入っていた。そうこうしている間に電車は都営地下鉄三田駅に到着し、ここからは直接成田空港を目指す。列車は京成成田スカイアクセス線直通の浅草線アクセス特急成田空港行。興味深いことに、この列車は京急線羽田空港第 1 ・第 2 ターミナル駅始発で、羽田空港と成田空港をダイレクトに結んでいる。朝 6 時でも空席はなかった。途中辛うじて着席はできたもののその後も混雑は増していき、千葉県に入った辺りで満員電車の様相を呈した。このまま全員成田空港へ向かうのかと思ってた矢先に過半数千葉ニュータウン駅で降車していったのには驚いた。

7 時半頃に空港第 2 ビル駅に到着し、ここからは LCC の発着する第 3 ターミナルへと向かう。第 3 ターミナルと駅のある第 2 ターミナルは少々離れていて、バスまたは徒歩で移動する必要がある。案内に従うままに外に出ると陸上のトラックのようなレーンがあり、これに沿って歩いて行くと第 3 ターミナルに辿り着ける。このトラックは利用客を滞らせることなく無意識に誘導する意味があるらしい。

成田空港第 2 ターミナル外。右の方に第 3 ターミナルへ続くトラックがある。

第 3 ターミナルまでは 10 分強で、それほど遠さは感じなかった。ターミナルに着くと、念のため持参していた服をもう一枚重ね着し、スマホとモバイルバッテリーを上着のポケットにしまった。チェックインはオンラインで済ませておいたため、すぐに保安検査場に向かうことができた。特に面倒なものは持ってきてなかったため保安検査もすぐに通ったが、問題ないのであれば髭剃りを持ってくれば良かった。保安検査場から搭乗口までも歩いて 10 分くらいの距離があった。搭乗口付近で手荷物の重量検査があり、内心ドキドキしていたが、蓋を開けてみれば 5.8 kg とそこそこ余裕があり胸をなで下ろした。時刻は既に 8 時 20 分を回っており、早めに来て正解だった。

8 時 45 分頃に搭乗が開始する。搭乗するのは Jetstar GK641 便長崎空港行。9 時ちょうど発 11 時 25 分着で、料金は全て合わせて 9,560 円。全日空日本航空と比べて安っぽい感じが悪くない。予約時点ではガラガラだったが、いざ乗ってみると機内は満席。座席間隔は狭く少し窮屈な感じもあったが、フライト時間も短いので大した問題ではない。滑走が始まるまではやや時間があったが、定刻通りに成田空港を出発。冴えない天気だったが、空の上では平等に晴天を享受できる。朝早かったためできれば機内で少し寝たかったが、地上を見て特徴的な地形から現在地を推定するのが面白くてほとんど眠れなかった。中でも富士山や木曽三川、琵琶湖、淡路島などははっきりと視界に捉えることができ、流石の存在感であった。2 時間というのはあっという間で、長崎空港上空に到達して着陸の準備が始まった。長崎空港大村湾の人工島の上に存在し、かなり海に近い位置で低空飛行を続けていたのでスリリングな面もあったが、定刻通りに無事到着した。

空港のある大村市長崎市は離れており、基本的にバスで移動する必要がある。時間があれば新大村駅から新幹線で行きたかったが、表彰式に遅れたら元も子もないので最速で長崎へ行くリムジンバスを選択。県内は大粒の雪が降っており、直前まで空の上で温泉気分になっていた身としては耐え難い寒さだった。バスは高速道路を経由して 45 分程走行し、12 時半頃に長崎駅前に到着。長崎駅周辺は新幹線開業に伴い大規模に再開発されており、市街の歴史ある街並みとはアンバランスな印象を受けた。

新幹線開業に伴い変貌を遂げた長崎駅

学会自体は 13 時から始まるため、まずは出島メッセ長崎へと向かった。会場は長崎駅の真横で非常にアクセスが良い。受付では本人確認はされず名前を言うだけで名札がもらえ、皮肉にもセキュリティがガバガバなのが面白かった。朝からほぼ何も食べておらず飢えており、市街へ繰り出す余力もなかったので駅ビルのラーメン屋で食事を済ませた。会場に戻ると既に招待講演が始まっていたが、実はその間に賞状の受け取り方を調べるなどしていた。

表彰式は 14 時に始まった。登壇者は会場前方へ集まるように言われ一応前の方に行ったが、当然知らない人ばかりなので結局中途半端な位置に留まってしまった。コメントを求められたら何て言おうかなと考えたりもしたが、実際はただ賞状を受け取るだけの簡単な仕事であった。

表彰式が終わると先生に会った。登壇しているときに目の前で撮影されていたようだったが、壇上からは全然気が付かなかった。セッションには一応全日行く予定だったが、先生に「この後はゆっくりしていってください」と言われたのですっかりその気になってしまった。その後は元研究室の人々と合流したが、M2 の先輩方を含め皆さんに認知してもらえているようで安心した(ぼっち回避)。

一段落すると荷物を置きにホテルにチェックインしに行った。今回のホテルは天然温泉鶴巻の湯ドーミーイン PREMIUM 長崎駅前の素泊まりプラン。建物は新築で、洗面所やトイレが広かったのが良かった。

ホテルの部屋(4 日間の家)

旅行に行くと初日が大事という感はあったので、ホテルで荷物を整理してから早速市街へ繰り出していった。雪は一粒一粒が大きく、発泡スチロールが降り注いでいるかのような感覚を味わった。歩いているといいことがありがちな海沿いを進み、最初は長崎港へと行く。港では軍艦島クルーズが運営されていたが、悪天候で海が荒れているため残念ながら 25 日まで 3 日間運休が決まっているようだった。海沿いを離れてからはオランダ坂や新地中華街の方を一通り歩き、路面電車に乗って長崎駅に戻る。途中で八百屋があり、店頭で大きいみかんが 4 個 100 円で売られていたので衝動買いした。

17 時からのセッションでは研究室の先後輩の発表があったのでそちらを聞きに行き、終了後は飲み会に行く流れになった。元々行こうとしていた店は相次いで満席で、結局私が泊まるホテルのすぐ横の居酒屋へ行くことになったが、外は雪が降りやまず非常に寒かったのでこれはありがたい。店では、他の研究室の先生の奇行で盛り上がった。特に、隣の研究室の先生が試験後に zoom で名前を隠しもせず公開採点を始めたというのには幻滅した。他では、国際会議が日本開催だと残念 → 直近の IMO が日本開催でそれと状況が似てそう、という流れで数オリ系の話の流れになった。先輩方が良問と評する今年の JMO 予選の幾何問題を聞いてそれを考えていたりしたが、紙がない状態では解けない。他の人たちは set with friends というゲームをやっていたりしていた。このゲームは、場にある 12 枚の図形が描かれたカードの中から、形、色、枠線の色の 3 要素が 3 つ全て一緒であるまたは 3 つ全て異なる 3 枚のカードを選ぶというもので、難しそうだ。時間はあっという間に過ぎて閉店の 22 時を迎えた。外で会計を待っている間も雪は降り注いでおり、酔いと寒さでおかしくなりそうだった。

ホテルに直行すると、酔いを醒ますまで先程の幾何問題を考えたが、図を描いてみるとあっさり相似が見えて解くことができた。ドーミーイン名物ともいえる夜鳴きそばがあったことを思い出し、食堂へ向かう。あっさりめの醤油ラーメンでいい感じに酔いが醒めてきたところで最上階の大浴場へ行った。ホテルの客室の風呂はユニットバスであることがほとんどで、湧かすのも面倒であるため、大浴場でしっかりと温まれるのは非常に良い。風呂から戻ってみかんを食べたりした後、24 時頃に就寝。本当に長い 1 日だった。

大会 2 日目 (1/24 水)

朝はゆっくりと寝て 9 時半頃に起床。本当はもっと寝たかったが、10 時まで営業している大浴場に間に合わせるためここで起きた。朝食はみかん 1 個で済ませた。風呂から戻ってきた後もなかなか動く気にはならず、11 時半頃に食事に行くために漸く部屋を出る。食事は前日の散策で見つけた海沿いの海鮮市場へ行った。他学科出身の研究室同期(所属していた時期は被っていない)2 人を誘ったら一緒に来てくれたが、1 人とはそれまで一切関わりがなかったのに仲良くしてもらえたのは嬉しかった。

海鮮市場 長崎港 出島ワーフ店にて食事

食後は少し散策し、例によって中華街の方面へ来た。雪はまだ降り続いており、この時が一番強かった。適当に歩いていると、カステラの文明堂に遭遇する。「カステラ一番、電話は二番、三時のおやつは文明堂」の CM が有名らしいが、私は全然認知していなかった。しかし同期がその CM をずっと歌っているので、すっかり脳にこびりついてしまった。土産は最後に買う予定だったが、せっかくなのでここで少々カステラを買うことにした。普通のカステラといちごカステラの 6 切れ詰め合わせで 820 円。1 切れ 120 円だったので、バラで買ったときと比べて高くなっていたが、これは包装代なのだろうかという疑問を持った。それにしては包装も普通の紙袋という感じであったが。

写真にも映るくらい大粒の雪が降り注ぐ新地中華街

カステラを一旦ホテルに置きに行った後、この日初めて会場へ向かった。しかし、聞こうと思っていたセッションはほぼ満席で気軽に入れそうな余地がなかったため、次のセッションが始まるまで駅ビルのカフェで待つことにした。ここでは、研究室の教授が担当している暗号の授業のレポート問題の 1 問を解いた。内容は、奇素数 p について、乗法群 (\mathbb{Z}/p^e\mathbb{Z})^\times巡回群であることを示せというものである。証明自体は初等的な議論で回るが、事実としてはなかなか興味深い。

15 時頃になり、次のセッションも近かったので再び会場へ戻った。このときには雪は降りやんでおり、晴れ間も垣間見えた。セッション開始には数分間に合わず結局立って見ることになった。このセッションでは研究室の先輩の発表があったが、その発表では行列の 01 情報から得られる二部グラフを利用しており、非常に興味深かった。他にも格子の SVP を用いた素因数分解法などの発表があり、全体的に自分の関心と合致しているセッションだった。

セッション自体は 17 時過ぎに終了。19 時頃から始まる懇親会まで時間があったので、研究室の数人と一緒に駅ビルのゲーセンに行った。一緒に行った 1 人は音ゲーをかなりやっている一方で、私は全然わからないのでプレイしている様子を見ながら研究の話など雑談をしていた。maimai や太鼓の達人をやっていたが、後者のコーナーには小学校入ってないくらいの子供がいて、しかもしっかりと叩けていたのでそれに目を奪われた。ギャラリーもできていて面白かった。色々な意味で将来が楽しみである。

夜になったら懇親会が始まった。参加費は 7 千円と少し高かったが、ビュッフェ付きである。珍しいものとしては鯨のベーコンがあったが、地域によっては学校給食に出てきたりするほど鯨は日常的に食べられているらしい。懇親会では指導教員以外の研究室の先生方と挨拶をしたが、認知してもらえていたのは安心した。その他にも他大学の学生と話をする機会があった。bayashiko さんら早稲田の競技プログラマーが多数所属する研究室の M1 の方もいらっしゃって世界の狭さを感じた。会場では長崎くんち伝統芸能である龍踊りの披露が始まる。龍と書いて「じゃ」と読む。昔は蛇の字があてがわれていたが、途中で変わったそうだ。辰年ということで龍を模しているのかと思ったが、そういうことではないらしい。

懇親会食事(抜粋)。茶碗蒸しが美味しかった

21 時頃に懇親会が終了した後は真っ直ぐホテルに戻った。ホテルではテレビをつけながらレポート問題の続きに取り掛かり、今度は Coppersmith 法の元論文を読んだ。無目的でテレビをつけるのは旅行のときに特有なので、非日常感があって良い。ニュースでは大寒波の様子が報道されていた。滋賀県長浜市では 50 cm 超の積雪だったようだ。その後もバラエティ系の番組を見たり、日課の夜鳴きそばと風呂をこなしたりして、2 時頃に就寝。

大会 3 日目 (1/25 木)

例によって 9 時半に起床し、大浴場へ直行。湯上がり処にヤクルトがあったので、それを部屋に持ち帰って飲んだ。この日も昼までダラダラした後食事へ向かう。食事は駅前のトルコライスの店ニッキーアースティン。トルコライスはトルコ発祥なのかと思っていたが、その由来は諸説あって釈然としないようだ。メニューはライスの種類が 5 つ以上、肉料理の種類が 20 個程度あって、組み合わせとしては 100 通りを優に超えていたように思う。メニューはライスの種類が 100 の位、肉の種類が下 2 桁に対応した 3 桁(中には 4 桁も) の番号で呼ばれており、混沌を極めていた。今回注文したのは 307 番。300 番台は「白いご飯にカレー、ドライカレースパゲティにデミソース」に対応し、下 2 桁の 07 は「チキンカツと煮込みハンバーグ」を意味する。とりあえず全てを盛り込んだというような見た目だったが、味は良かった。

トルコライス 307 番

食後は会場へ向かい、連日食事に行った同期の発表を見た。私は研究テーマの決定すらままならない現状なので、この時点で学会発表できているのはすごい。発表を色々見ての感想だが、研究室の人たちはしっかり指導されているだけあって流れの掴みやすい発表がなされていると感じた。

セッションが終わった後は同期 2 人が一緒に泊まっているホテルの部屋へお邪魔した。最上階で、広く眺めの良い部屋だったが、直前にキャンセルが出たおかげで部屋が格上げされたとのことだった。ホテルのテレビは YouTube が見れたので、朝日杯の▲近藤誠也△西田拓也戦の中継を視聴。先手の近藤七段がミレニアムに堅く囲って優位に進めていたが、終盤西田五段の 75 歩を打ってから 57 馬と引く手順が巧妙で将棋は激戦に。最後まで見たかったが、千日手模様になったので切り上げて皆で散歩に行くことにした。

天気も良かったので、2 人の要望もあってグラバー園へ赴いた。私は 6 年程前に一回行ったことがあるが、その時は夜だったのでまた別の空気を味わえるかという期待を抱いた。この庭園は、歴史ある建造物なのにエスカレーターや動く歩道があるのが面白い。一通り園内を巡ったが、やはりドッグハウスから眺望できる街並みはとても良かった。コーヒーだけで千円するという噂の自由亭喫茶室にも行ってみたかったが、あいにく営業時間外になっていた。最後に土産処があったので、ここではカステラプリンを購入した。

グラバー園から望む長崎湾

夜はグラバー園からも程近い新地中華街で食事をした。中華街は意外と人通りが少なく閑散としていた。食事は長崎ちゃんぽんの店で皿うどんと餃子を食べた。味は良かったが、若干甘さが強かった印象を受けた。

皿うどん

同行していた 1 人は明日の発表のスライド作りのためホテルに戻ってしまったので、もう 1 人と一緒に稲佐山山頂へ行くことにした。研究室の Slack でも誘いをかけたが、全く反応がなく悲しかった。稲佐山長崎駅の西側にある山で、長崎駅から 15 分程歩いた場所にある淵神社から出るロープウェイによって登頂できる。山頂は凍てつくような寒さで、写真を撮るだけで手が痛くなってしまう程だった。しかしながら世界三大夜景にも登録されている長崎の夜景は格別で、坂の多い市街の織りなす立体的な景色にしばし見惚れた。15 分ごとにハートマークや龍を象った電装が点灯するという演出もあった。もう少し山頂を散策したいという思いはあったが、寒さに耐えきれず 30 分程で引き返す。帰りはロープウェイ乗り場近くからバスに乗って駅まで戻った。

長崎の夜景

21 時頃にホテルに戻った後は、すぐ入浴をした。冷えた後の風呂は心地が良い。数十分かけて上がった後は、湯上がり処のアイスと部屋備え付けのプリンを嗜みながら荷物の整理をした。4 日というのは長いようで短く、短いようで長い。市街を観光するには過不足ない日数だったと思う。土産が増えることを鑑み、重量制限を気にしたくなかったので帰りはホテルから宅急便で荷物を送ってもらうことにした。テレビを見たり仕事をしたりしつつ、2 時頃に就寝。

大会 4 日目 (1/26 金)

4 日間の学会もついに最終日。この日は自分の卒論を引き継いで研究をしている同期の発表があり、それは絶対に見ておきたかったので 8 時に起床。最後の風呂に入り、最後のみかんを食べてホテルを後にする。部屋がエレベーターホールから一番離れた所だったという点以外は満足の高いホテルだった。チェックアウトした後急いで会場へ向かったが、セッション開始には 3 分程遅れてしまった。風呂にゆっくり浸かりすぎたのが敗因だ。同期の発表に対してどういう質問コメントが飛んでくるのか注視していたが、仮定に対する技術的なアドバイスがされていてこちらとしても勉強になった。論文を引用している先生からこうして直接コメントを頂けるというのは大変ありがたい。

全てのセッションが終わった後、研究室のメンバーで集まった。他の人たちは前日レンタカーで佐世保まで行っていて、帰りに稲佐山にも寄っていたようだった。それならそうと教えて欲しかった感はある。終了後は物産館や駅ビルでカステラなどを買い足しし、帰りの空港の福岡まで行く必要があったのでここで別れた。

博多駅までは西九州新幹線で向かった。これに乗るために帰りの空港を福岡空港にしたと言っても過言ではない。13 時 45 分発の西九州新幹線かもめ 32 号武雄温泉行に乗車。車両は 6 両編成の N700S 系で、トイレのタイル見たいな床が特徴的だった。武雄温泉駅佐世保線長崎本線の特急リレーかもめ号に接続し、博多駅までを結ぶ。所要時間は新幹線が約 30 分、リレーかもめ号が約 1 時間の合計約 1 時間半で、料金は乗車券 2,860 円、特急券が通しで 2,660 円の合計 5,520 円だった。新幹線内はあっという間だったので新鳥栖まで全線開業すればかなり早く着けそうに思ったが、佐賀県の反対で着工できていないというのが現状である。なお、当の佐賀県知事の息子が高校同期という話もある。

西九州新幹線 N700S 系車内。トイレよりもトイレっぽい床。

博多駅に着いたのは 15 時過ぎで、飛行機の搭乗までは時間があったのでまずは食事を求めに中洲へ繰り出して行った。福岡はほとんど来たことがないので土地勘はなく、とりあえず中洲川端駅まで歩いてきたものの辺りにはチェーン店しかある気配がない。程なくして店は中州の博多駅方面の側にあることに気づき、そちらへ向かった。この辺を歩いてみると実際に「中州」になっていることを実感できる。時刻は 16 時台で、昼の営業は終了し夜の営業はまだ始まっていないという所が多く、営業中の店が少なかったためとりあえず開いている店に飛び込んだ。鶏皮餃子というメニューが目を引いたので注文したが、パリッとした皮の中に餃子のタネが詰め込まれており非常に美味しかった。せっかく福岡に来たので、ちゃんと食事ができてよかった。

豚骨ラーメンと鶏皮餃子(初見)

飛行機まではまだ時間があったので福岡タワー北側の百道浜へ行った。こちらは以前福岡タワーに登った際に見えて、一度行ってみたいと思っていたのでちょうど良かった。福岡タワー周辺はアクセスが優れず、地下鉄空港線唐人町駅から PayPay ドームを経由して 20 分程歩いてももち海浜公園に到達した。海に突出している建物マリゾンは謎が多い。

海浜公園。奥には漢委奴国王の金印で有名な志賀島も見える

浜辺を後にすると、今度こそ福岡空港へ向かった。博多駅から福岡空港駅までは空港線で 2 駅と非常に近かったが、博多駅までは意外と時間を要した。事前チェックインを済ませておいたので、空港では淀みなく保安検査に行く。保安検査では土産の袋を勝手に倒されてビリビリに破かれたので少し気分を害した。福岡空港では一切重量計測はされず、搭乗口までも近かったため暇を持て余してしまった。時刻は 18 時半頃で、搭乗の 19 時 35 分まで一時間以上ある。しかも他の東京方面行の便は遅延が出ていたので、私が乗る便も遅延したら嫌だなという気持ちになった。保安検査をくぐってしまうともう店もあまりなく、本当にやることがなかったので将棋をやったり競プロの問題を考えたりしていたが、それなら博多南駅など行きたいところに行けば良かった。

飛行機自体は定刻通り 19 時 35 分に出発した。Jetstar GK516 便の成田空港行。到着予定時刻は 21 時 20 分 で、所要時間は往路と比べて大分短いが、これは偏西風の影響である。料金は 8,560 円で、長崎からの移動込みでも安い。今回は使わなかったが、私用であれば JALスカイメイトも使っていきたいと思った。今回の座席の目の前には非常口があった関係で前の座席は抜け番となっており開放感があったが、荷物は全て上の棚に上げなければならず、脱いでいた上着を没収された。LCC だからなのかはわからないが、全体的に荷物の扱いが雑な印象を受けた。フライトはつつがなく、予定よりも早く成田空港に到着。その後も実は色々話があったが、今回はここまでとさせていただきたい。

感想

遠出しての学会は初めての経験でしたが、気になる発表を見つつ観光もするという両立をいいバランスでできたので、非常に充実した 4 日間になりました。

ラミナーマトロイド

ICPC 2023 アジア地区予選 横浜大会で、本質的にラミナーマトロイドの最小重み基を求める問題が出題されたので、その復習をしていきます。

ラミナーマトロイド

ラミナー族

集合 E の部分集合族 \mathcal{F} は、以下の条件を満たすときにラミナー族と呼ばれます。

  • 任意の E の部分集合 A,B\in\mathcal{F} について、A\backslash B,\;B\backslash A,\;A\cap B の少なくとも 1 つが空集合である。

ラミナー族は競技プログラミングでも馴染みの深い概念です。例えば、頂点集合を V とする根付き木について、頂点 v を根とする部分木に含まれる頂点集合を T(v)\subseteq V とすれば、V の部分集合族 \left\{T(v)\mid v\in V\right\} はラミナー族となります。

部分木から構成されるラミナー族

ラミナーマトロイド

集合 E とそのラミナー族 F について、容量関数 c:\mathcal{F}\to\mathbb{Z}_{\geq 0} があるとします。このとき、E の部分集合族 \mathcal{I}

 

 \displaystyle \mathcal{I}:= \left\{X\subseteq E\mid\forall A\in\mathcal{F},\; |X\cap A|\leq c(A)\right\}

 

と定義します。このとき、(E,\mathcal{I})\mathcal{I} を独立集合とするマトロイドになります。このように、ラミナー族とその容量関数を用いて表現できるマトロイドはラミナーマトロイドと呼ばれます。

マトロイドであることの証明*1

\mathcal{I} がマトロイドの独立集合の公理、つまり以下の 3 条件を満たすことを見ていきます。

  1. \emptyset\in\mathcal{I} である。
  2. Y\in\mathcal{I} かつ X\subseteq Y ならば、 X\in\mathcal{I} である。
  3. X,Y\in\mathcal{I} かつ |X|\gt|Y| ならば、ある x\in X\backslash Y が存在して Y\cup\{x\}\in\mathcal{I} である。

c の非負性から、条件 (1),(2) は明らかに成立します。

条件 (3) が成立することを確認するために、X,Y\in\mathcal{I}|X|\gt |Y| を満たすとします。\mathcal{A}:=\{E\},\;A:=E と初期化し、以下の操作を繰り返し行います。

  • \mathcal{F}_A:=\left\{Z\in\mathcal{F}\mid Z\subseteq A\right\} とする。A\in\mathcal{F}_A または \mathcal{F}_A\subseteq\{\emptyset\} のときは操作を終了する。
  • そうでない場合は \mathcal{F}_A の極大集合を A_1,\dots,A_t とし、A_0:=A\backslash(A_1\cup\dots\cup A_t) とする。\mathcal{F} がラミナー族であることより A_0,\dots,A_t は互いに交わらないことが保証される。|X\cap A|\gt |Y\cap A| なので、|X\cap A_i|\gt |Y\cap A_i| を満たす i\in \{0,\dots,t\} が存在する。この i を用いて \mathcal{A}\leftarrow\mathcal{A}\cup\{A_i\},\;A\leftarrow A_i と更新する。

1 回の操作で A の要素数は必ず小さくなるので操作は有限回で終了します。また、操作終了後の A|X\cap A|\gt |Y\cap A| を満たすので、x\in(X\backslash Y)\cap A を満たす x を取ることができます。この x について Y\cup\{x\}\in\mathcal{I} となります。

実際、|(Y\cup\{x\})\cap B|\gt|Y\cap B| を満たす B\in\mathcal{F}B\in\mathcal{A} であるので、|(Y\cup\{x\})\cap B|\leq|X\cap B|\leq c(B) が成り立ちます。

これで条件 (3) の成立も言えたので、(E,\mathcal{I}) がマトロイドになっていることが確認できました。

出題例

ICPC 2023 アジア地区予選 横浜大会 J 問題が、ラミナーマトロイドの最小重み基を求める問題となっていることを見ていきます。考察をすると、元の問題が以下の問題と等価であることがわかります。

 

頂点集合を V とする根付き木と f\in\mathbb{N}^V が与えられる。頂点 v を根とする部分木に含まれる頂点集合を T(v)\subseteq V とするとき、以下の値を求めよ。


\min\left\{\sum_{v\in V} f_vx_v^2\;\middle|\;\begin{array}{l} x\in\mathbb{Z}_{\geq 0}^V\\ \sum_{v\in V}x_v = |V|\\ \sum_{u\in T(v)}x_u\leq|T(v)|\;(\forall v\in V) \end{array}\right\}

 

V の要素数n として、集合 EE:=V\times\{1,\dots,n\} と定めます。さらに、E の部分集合族 \mathcal{F}

 

\displaystyle\mathcal{F}:=\left\{T(v)\times \{1,\dots,n\}\mid v\in V\right\}

 

とすると、これはラミナー族になっています。容量関数 c:\mathcal{F}\to\mathbb{Z}_{\geq 0} と重み関数 w:E\to\mathbb{Z}

 

\displaystyle c(T(v)\times \{1,\dots,n\}):= |T(v)|

 

\displaystyle w(v,k):= f_v(2k-1)

 

とすれば、この問題は前述したマトロイド (E,\mathcal{I}) における w についての最小重み基を求める問題として表現できます。従って、貪欲法の最適性が示されます。

参考文献

[1] T.Fife and J.Oxley. Laminar matroids. European Journal of Combinatorics 62 (2017): 206-216.

*1:証明が載っている文献が見つからなかったので、自分で書いています

SQLite のウィンドウ関数

SQLite のウィンドウ関数の使い方についてまとめます。

ウィンドウ関数の機能

SUMMAX などの集約関数の後ろに OVER 句をつけることで、列の値全体を使った計算をすることができます。集約を行うグループごとに 1 行にまとめられるのではなく、各行のデータを残したまま計算結果を付与できるのが GROUP BY 句を使う場合との大きな相違点です。

使い方

ウィンドウ関数は以下のような構文で使うことができます。

aggr(column_1) OVER(PARTITION BY column_2 ORDER BY column_3)

ここで、aggrSUMMAX などの周約関数です。

PARTITION BY

column_2 の値ごとに分類した後に集約することができます。ここは GROUP BY column_2 で集約するのと同様です。

PARTITION BY 句を省略することもでき、その場合は全体が 1 つのグループとなります。

ORDER BY

column_3 をキーとしてソートした後に、先頭行から該当する行までだけを集約することができます。この ORDER BY 句の後に ROWS BETWEEN x PRECEDING AND y FOLLOWING と続けると、該当する行の x 行前から y 行目後まで (inclusive) だけを集約することもできます。より詳細な行の指定方法については、以下の記事が参考になります。

分析関数(ウインドウ関数)をわかりやすく説明してみた #MySQL - Qiita

ORDER BY 句を省略することもでき、その場合は PARTITION BY 句で分割されたグループごとに全体を集約します。

使用例

以降では、このウィンドウ関数の具体的な使用例について見ていきます。

順位付け

以下の表 TITLE_RANKING において、TITLE の降順に順位を付与することを考えます。

NAME      STATE   TITLE
--------  ------  -----
fujii     active  19
habu      active  99
nakahara  retire  64
ooyama    retire  80
tanigawa  active  27
watanabe  active  31
yonenaga  retire  19

集約範囲を全体にして RANK 関数を適用すればよいです。

SELECT
    *,
    RANK() OVER(
        ORDER BY TITLE DESC
    ) AS RANK
FROM
    TITLE_RANKING
ORDER BY
    STATE ASC,
    RANK ASC;
NAME      STATE   TITLE  RANK
--------  ------  -----  ----
habu      active  99     1
ooyama    retire  80     2
nakahara  retire  64     3
watanabe  active  31     4
tanigawa  active  27     5
yonenaga  retire  19     6
fujii     active  19     6

STATE で分類して順位をつけることもできます。

SELECT
    *,
    RANK() OVER(
        PARTITION BY STATE
        ORDER BY TITLE DESC
    ) AS RANK
FROM
    TITLE_RANKING
ORDER BY
    STATE ASC,
    RANK ASC;
NAME      STATE   TITLE  RANK
--------  ------  -----  ----
habu      active  99     1
watanabe  active  31     2
tanigawa  active  27     3
fujii     active  19     4
ooyama    retire  80     1
nakahara  retire  64     2
yonenaga  retire  19     3

累積和

以下の表 POST_RECORD において、POST の累積和を取ることを考えます。

YEAR  MONTH  POST
----  -----  ----
2022  7      1
2023  8      3
2023  9      3
2023  10     1
2023  11     2
2023  12     2

時系列でソートし、先頭から該当行までを集約して SUM 関数を適用することで、以前の POST の総和を計算することができます。

SELECT
    *,
    SUM(POST) OVER(
        ORDER BY YEAR ASC
        MONTH ASC
    ) AS POST_SUM
FROM
    POST_RECORD
ORDER BY
    YEAR ASC,
    POST_SUM ASC;
YEAR  MONTH  POST  POST_SUM
----  -----  ----  --------
2022  7      1     1
2023  8      3     4
2023  9      3     7
2023  10     1     8
2023  11     2     10
2023  12     2     12

差分計算

以下の表 R_TRANSITION において、前回からの R の変動値を計算します。

DATE        R
----------  ----
2023-06-18  2796
2023-07-02  2802
2023-07-31  2816
2023-08-14  2853
2023-12-18  2833

ウィンドウを DATE でソートしたときの 1 行前のみに設定して MAX 関数 (MINSUM でも良い) を適用することで、直前の値を取得することができます。

SELECT
    *,
    R - MAX(R) OVER(
        ORDER BY DATE
        ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
    ) AS DELTA
FROM
    R_TRANSITION
ORDER BY
    DATE;
DATE        R     DELTA
----------  ----  -----
2023-06-18  2796
2023-07-02  2802  6
2023-07-31  2816  14
2023-08-14  2853  37
2023-12-18  2833  -20

まとめ

関数型言語っぽさがありつつも表現力が高いので面白いですね。