KCPCブログ

京大競プロサークルKCPCのブログです!

【ABC437】早解き必須回にて躓きまくり……

今週の参加記担当の山田です!

ABC437

クリスマスが近づいています。競プロ界隈でクリスマスといえばhosさんの恒例コンテストですが、ABC437もクリスマスのサブタイトルが付いています。ちなみに12/24はSASUKEが見たいのでXmas Contestの方には泣く泣く不参加です。

コンテスト開始時点での山田のレーティングは2020だったので今回もRated対象外での参加記となります。

黄タッチ後の山田 vs ABCの歴史を振り返ってみると、カンスト2回、黄perf3回、青perf2回、水perf1回。まだ黄色安定とはそこまで言えないので黄perfが欲しいです。

コンテスト告知

爆速6完必須回になる確率が高い配点の上、3人全員が警戒しているWriterなので怖いです。

A問題

問題文: A - Feet

difficulty: 43?

KCPCメンバー(Rated参加)のAC率: 14/14

print(A*12 + B)。0:30 AC

AtCoderの提出の折り畳み使用が変わりました(長すぎると『拡げる』を押しても完全には開かない)。山田を含む、テンプレ激長族のコードが見にくいです。

B問題

問題文: B - Tombola

difficulty: 112?

KCPCメンバー(Rated参加)のAC率: 14/14

コンテスト後に問題名の元ネタを調べたら、イタリアの年末パーティでよく行われる運ゲーらしいです。

この気持ち悪い制約も『tombola』の再現です

要素が相異なるという制約がなかったらちょっと気を付けることが増えます。この問題は何も考えなくても合わせられました。

1:40 AC

C問題

問題文: C - Reindeer and Sleigh 2

difficulty: 643?

KCPCメンバー(Rated参加)のAC率: 14/14

350点です。難しいC問題に付けられる配点です。C問題だから大丈夫だろうと言いたいところですが、ABC432Cに30分かかる大事件が最近起こったので気を引き締めます。

サンタとトナカイを選ぶ問題かと思いきや、あろうことか登場人物が全部トナカイです。

そして案の定パッとわかりません。

トナカイを一頭ずつ追加していくことを想像してみると以下のDPが有効そうです。

 \displaystyle
dp_{i,w,p} = (トナカイi未満まで見て、重さ合計w、力合計pのときの最大搭乗トナカイ数)

ですがこれだと到底間に合わないです。

dpの形跡から答えを抽出するパートを想像すると、wpの差が非負であればいいので、無駄な情報が相当に多いです。よって以下のようにします。

 \displaystyle
dp_{i,j} = (トナカイi未満まで見て、『重さ合計-力合計』がjのときの最大搭乗トナカイ数)

一次元減らせましたがまだだめです。

ここまでの考察は一瞬で、ここから躓きました。どうやっても高速化不可能です。こういう『値をスライドしてchmax』みたいなDPを高速化する方法は知りません。あったとしても横幅が広すぎて厳しそうです。

DP方針は捨てた方がいいとすぐ判断しました。

 \sum P - \sum W \geq 0というこの問題を表す式でいろいろできないかしばらく考えてみました。しかし変形の余地はありません。

改めて問題設定を考え直してみます(詰まった時は設定の考え直しが有効になりがち)。これは『ソリに乗るトナカイ』という部分集合と『ソリを引くトナカイ』という部分集合を選ぶ問題です。二つの部分集合は競プロ方言でいわゆる分割型です。つまり各要素がちょうど1つの部分集合に属するという形です。

よって選ぶ部分集合は1つで済みます(「全員『ソリを引く』状態がデフォルト。『ソリに乗る』状態にする部分集合を選んでいく」ということになります)。選ばれる要素ごとに \sum P - \sum W差分が固定されているので、それが小さい順に貪欲でソリに乗せていけばよい、ということがやっと分かりました。難しくないですか!?

実装開始。

WPは、それを合体したstd::pairの配列で受け取ります。std::sortラムダ式を投げるテクでソートします。(C++erで知らない方は調べてみることをおすすめします)

差分の計算が思ったよりもミスりやすく、サンプルを合わせるのに苦労しました。算数弱者。

10:56 ACまあ遅すぎではない!

初心者も少なくないKCPCの正答率が100%なのは凄いと思います。本当になんで……?

D問題

問題文: D - Sum of Differences

difficulty: 664?

KCPCメンバー(Rated参加)のAC率: 13/14

とりあえずAの要素を固定してみてaとして、答えへの寄与を考えます。つまり

 \displaystyle
\sum_{j} |a-B_j|

という式で表される値です。これをO(logN)以内ぐらいで求めたいです。

求める式のシグマの中身に絶対値やmaxやmodみたいな記号はあったら邪魔です。逆にこれを場合分けなどによって消せればすぐ解けがちです。そういう問題は大量に見覚えがあります。

絶対値はaB_jの大小関係で場合分けすれば外せます。Bはあらかじめソートしておきます。a以上になる瞬間のBの添字(std::lower_boundで求める)をmとすると、

 \displaystyle
\sum_{0\le j< m} (a-B_j) + \sum_{m\le j< M} (B_j - a)

これならBの累積和を使って定数時間で求まります。これを全てのaに渡ってやります。

13:16 AC今日の癒し枠でした。

E問題

問題文: E - Sort Arrays

difficulty: 1348?

KCPCメンバー(Rated参加)のAC率: 9/14

根付き木に見立てたくなる設定です。

そもそも木の入力の与えられ方は、

制約
1 ≤ u_i, v_i ≤ N

入力
u_1 v_1
u_2 v_2
...
u_{N-1} v_{N-1}

というものの他に、

制約
1 ≤ p_i < i

入力
p_2 p_3 ...  p_N

という風に『親頂点』で与えられるものがあり、この問題のx_iの与えられ方と一致しています。

そうしてx_iの値によって構築した木は、まさにTrieと酷似しています

ですが完全に一致しているわけではありません。Trieは同じ列であれば同じ頂点に所属する、という性質がありますが、入力からそのまま作った木だと同じ数列があっちこっちに発生します。

最終的に辞書順に並べることを考えるとTrieでできます。よって構築すべきはTrieの方だろうと考えました。考察はこれで終わりです。

Trieライブラリは手元にありますが、そのままは使えません。いつもそういう場合はコピペ後に実装を書き足して使用するという戦略を取っています。ですが今回はほとんど0から書き直さないとならなそうです。Trieは自分で作った方がよさそうです。嫌だ……。

実装前に、実装方針を考察用紙に書き出します(最近、実装が重そうなときはこれを徹底しています。実装崩壊率は体感減っています)。

構築すべきものは、

  • vector<map<ll, int>> g(頂点番号を入れると、遷移先を表す連想配列が返ってくる)

  • vector<vector<int>> lis(頂点番号を入れると、そこに所属する数列番号の一覧が返ってくる)

です。(これ一緒の構造体にした方が分かりやすかったかな……)

これだけ持っておけば大丈夫だろうかと考えてみると、大丈夫じゃないです。『「親の数列x_i」が所属する頂点』が高速に知りたくなるので、

  • vector<int> pos(数列番号を入れると、それの所属先である頂点番号が返ってくる)

という配列も必要です。以上の3つの変数を同時に動かしながら構築していく必要があります。『同時に動かす変数』の数が多いと指数関数的に実装が大変になるのですが、3つならなんとかなりそうです。

と、思っていましたが配列のサイズも動くので配列外参照が発生したりして、サンプルを合わせるのに時間がかかりました。あと添字が2種類あるので混同して大変でした。

32:27 RERE!?

WAもありました。なんでだ。睨みデバッグでは原因がすぐ分からなかったので、必殺・ランダムチェッカーを発動させました。

山田は以下の2種類のシェルスクリプトを使っています。(コンテスト中のものとは少し違います。このブログに載せるついでにGPTと一緒に修正しました)

a.sh

#!/bin/bash

g++ -std=gnu++20 -O2 a.cpp -o a.out || { echo "a.cpp コンパイル失敗"; return 1; }
echo "a.cpp コンパイル成功"
g++ -std=gnu++20 -O2 n.cpp -o n.out || { echo "n.cpp コンパイル失敗"; return 1; }
echo "n.cpp コンパイル成功"
g++ -std=gnu++20 -O2 r.cpp -o r.out || { echo "r.cpp コンパイル失敗"; return 1; }
echo "r.cpp コンパイル成功"

cnt=0
while true; do
    ./r.out > inp
    ans1=$(./a.out < inp)
    ans2=$(./n.out < inp)
    cnt=$((cnt+1))
    if [ "$ans1" != "$ans2" ]; then
        echo "Wrong Answer on case $cnt"
        echo "===== input (inp) ====="
        lines=$(wc -l < inp)
        if [ "$lines" -le 20 ]; then
            cat inp
        else
            head -n 20 inp
            echo "..."
        fi
        echo "======================="
        echo "a.out:"
        echo "$ans1"
        echo "n.out:"
        echo "$ans2"
        break
    else
        echo "Case $cnt OK"
    fi
done

r.sh

#!/bin/bash

g++ -std=gnu++20 -O2 a.cpp -o a.out || { echo "a.cpp コンパイル失敗"; return 1; }
echo "a.cpp コンパイル成功"
g++ -std=gnu++20 -O2 r.cpp -o r.out || { echo "r.cpp コンパイル失敗"; return 1; }
echo "r.cpp コンパイル成功"

cnt=0
while true; do
    ./r.out > inp
    ./a.out < inp > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo "Runtime Error on case $cnt"

        echo "===== input (inp) ====="
        lines=$(wc -l < inp)
        if [ "$lines" -le 20 ]; then
            cat inp
        else
            head -n 20 inp
            echo "..."
        fi
        echo "======================="

        break
    fi
    cnt=$((cnt+1))
    echo "Case $cnt OK"
done

前者a.shは、提出用コードa.cppと、愚直解n.cppに、ランダムテスト生成コードr.cppで入力しまくって、出力が不一致となる入力を見つけるスクリプトです。後者r.shは、提出用コードa.cppに、ランダムテスト生成コードr.cppを入れまくってREの起こる入力を見つけるスクリプトです。

今回はREの起こる入力が知りたいので、使うのは後者のr.shです。これなら愚直解を書かなくて済みます(なのでREで落ちるときのランダムチェックは楽です)。このファイルをカレントディレクトリに置き、r.cppにテストケース生成コードを書き、. r.shで実行、強制終了させたいときは<C-c>vim方言でCtrl+cのこと)を押します。

無事落ちるケースが発見できました。inpというファイルに落ちた入力が保存されているのでそれを見たり./a.out < inpでプリントデバッグしていきます。すると一か所、 さっきの提出コード上に頂点番号と数列番号を混同している処理を見つけました。サンプルで落としてください……。

38:24 AC

25分+1ペナ。かかりすぎ! 添字筋が弱い。

F問題

問題文: F - Manhattan Christmas Tree 2

difficulty: 1390?

KCPCメンバー(Rated参加)のAC率: 10/14

マンハッタン距離は45度回転が叫ばれる世の中ですが、何でもかんでもマンハッタン距離を真っ先に回転させるのは良くないと考えています。回転後の座標でばかり考えて沼ったことが複数回あります。そもそも45度回転ってそんなに見かけないです。FFTとかより全然使いません。

そういうことでしばらく元の問題の状況を観察してみました。……しかし『二次元座標の集合に添字の区間クエリを施す』という独特すぎる要求の扱いがわかりません。x座標y座標の値の絡み方がいやらしいです。

45度回転する嬉しさのひとつは1次元の独立な問題に分解できがちことです。結局この問題は積極的に45度回転させた方が良さそうです。

回転後の点からの各チェビシェフ距離(つまり回転前のマンハッタン距離に同じ)を考えると、クエリ2の答えの候補は4方向に最も遠い点の4つに絞られます。これは(x座標, y座標)×(のmin, のmax)をそれぞれ取得できるセグ木(つまり4種類)を管理して更新クエリにも対応できます。(今考えると、マンハッタン距離の等距離線はひし形になるので、唐突に45度回転を持ち出さなくても、『斜め4方向に一番遠い点になるだろう』という予測は可能だった気がします)あと答えは最終的にマイナスにならないので、『上方向の点が存在しない場合』とかは考えなくてOKです。

簡単だこれ!早く実装しないと!!

山田はセグ木を生やす際、いちいちopなどの関数を作るのではなく、ベタな遅延セグ木を1秒で持ち出せるようにしています(ライブラリとかエディタを駆使します)。(区間更新、区間加算)×(区間和取得、区間min取得、区間max取得)の6種類は一瞬で出てきます。さらに同時使用してもテンプレート引数名は衝突しません。まさに山田の競技環境が役に立つ問題です。今回は遅延評価は必要ないですがこれを利用します。

47:38 ACとりあえず大崖のふもとまで到達。

恐る恐る順位表を開くとギリギリ黄色perf!良かった。でもペナルティで下がりまくりそう。

そしてG問題のACメンツがカラフルです。人間と思われるユーザーにほとんど解かれていませんでした。

G問題(読んだだけ)

問題文: G - Colorful Christmas Tree

difficulty: 2754?

KCPCメンバー(Rated参加)のAC率: 0/14

解けそうにないですけど読んでみました。一見木DPでできそうに見えます。でも根方向から切断していく状況に対応できません。

わかりません。Unratedなこともあり虚無な時間が流れて嫌だったので途中からは撤退しました。

再難所は復元パートらしいですが、そもそも求値のフローにすら辿り着けず……。難しいという先入観がなければ求値はできた可能性はあります。

Upsolveしたら何か追記するかも。

コンテスト結果

成績証

47分38秒+1ペナの6完。パフォーマンスは2042相当です。C問題で小炎上、E問題で大炎上して今日はダメかと思っていましたが、DとFが速かったらしく普通の結果となりました。

ABCならちょっと失敗したくらいでは黄perfラインまで下がらないぐらいにはなっており成長を感じます。

順位の推移

わかりやすくCとEに時間かかってます。DFを先に解いた人が多いのもありますが。こういう問題しか出題されないコンテストがあったら詰むのでそれが怖いです。

↓画面録画

- YouTube

【ABC436】スイングバイ成功なるか?【KCPCブログ】

はじめに

こんにちは!jastawayです。ABC436の参加記です!!

これは12/13に行われたABC436の参加記です!前回のABC437ではありません!!!

参加記が遅くなってすみません🙇

最近私は12/6,7に行われたICPC Yokohama Reigional 2025に京大チームbogosortで出場し、なんと全体5位の成績を修めることができ、3月頭に台湾で行われるplayoffに進出することができました!!

ICPCの参加記もKCPCブログで書いたのでぜひ御覧ください

kcpc.hatenablog.com

目次

ABC436

参加前の私のレートは1997でした。

前回の私のブログ担当ABC428で黄色復帰したはずでは?

はい。ARC二連敗で無事落青しました・・・ $n$ 回目

青黄反復村からは脱出できなかったようです・・・

前回のABC435はICPCのコンテスト前日でホテルから参加し、なんとか勝てましたがギリギリ黄色復帰ならず。

逆に今回カンストパフォを取ればスイングバイに成功し、一気にレートを盛るチャンスでもあります。

今回の配点は100-200-300-400-475-500-600でFまでの早解きが必須でしょう。Gはワンチャン?

二連AGCの参加権を得るためにも早く黄色復帰したい!

以下、各問題の考察&感想です。

A問題

問題文: A - o-padding

KCPCメンバー(rated 参加)のAC数: 11/11

推定diff: 47

$S$ について長さが $N$ 未満の間末尾にoを追加することをしました。

サンプルを見ると、末尾ではなく先頭に付けないといけないと誤読に気づき、追加の前後で $S$ をreverseして対処しました。

0:46に提出、AC。

B問題

問題文: B - Magic Square

KCPCメンバー(rated 参加)のAC数: 11/11

diff: 169

$N \times N$ の配列を用意し、本当に言われたとおりに書きます。

こういう、本当に言われたとおりにやるだけの問題について、今回は問題も0-indexedなので特に言い換えは必要ありませんでしたが、1-indexedでもindexの計算は問題文の通りにして、配列に操作するときだけ気をつけるようにすることが多いです。

3:49に提出、AC。

C問題

問題文: C - 2x2 Placing

KCPCメンバー(rated 参加)のAC数: 11/11

diff: 440

$R_i, C_i$ が大きいので、取りうるすべてのマスの情報を持つのは無理です。

各マスが現在選ばれているかどうかの情報だけが必要なので、集合を扱うデータ構造にこれまでにブロックが置かれたマスを管理して、あるマスが集合に含まれるかを高速に判定すればできます。

私はC++set<pair<long long, long long>>を使いました。

5:59に提出、AC。

D問題

問題文: D - Teleport Maze

KCPCメンバー(rated 参加)のAC数: 10/11

diff: 802

ワープも含めてすべての行けるところを考えてBFSをすると、同じワープが大量に合ったときに辺の数が多すぎてTLEしそうです。

そこで、各文字に対応する超頂点を追加して、ワープはこの超頂点を経由して移動するものと考えると、頂点も辺の数も少なくすみます。

超頂点への重みを0、超頂点からの重みを1とすると01BFSで解けるので、dequeなどを用いて実装しました。

16:03 に提出、AC。

E問題

問題文: E - Minimum Swap

KCPCメンバー(rated 参加)のAC数: 8/11

diff: 1077

前から順番にswapを繰り返すと最小の操作回数が達成できます。

しかし1回目の操作としてあり得る個数はよくわかりません。

上の最適操作によって、一致する個数は1個か2個増えるので、このようなものだけではと思い実装してみますが、サンプルが合いません。

この方針でしばらく考えて、後から一気に2個合わせられるようになる場合などを考えると難しそうとなりました。

そこで、頂点 $i$ から $P_i$ に辺を張ったfunctional graphを考えて、この操作がグラフをどう変えるのかを考えてみました。

このグラフはいくつかのループのみからなるグラフで、最終状態はループが $N$ 個のグラフです。

ここで最適操作を考えると、現在の1つのループを2つに分けるものだとわかり、これはループのどの2辺を選んでもいいので、初期のループ $i$ の長さを $L_i$ とすると、答えは $\sum_{i} L_{i} (L_{i} - 1)/2$ となります。

これは適当にDFSをするとできます。

50:39 に提出、AC。

結構時間をかけてしまいました・・・

F問題

問題文: F - Starry Landscape Photo

KCPCメンバー(rated 参加)のAC数: 6/11

diff: 1407

問題文の読解が少し難しかったです。解法は結構すぐに思いつきました。

明るさが最も大きいものがから順に考えます。

今、星 $i$ についてこれを集合に入れる場合を求めます。明るさがこれ以上のものについて入れるか入れないかを考えます。

$i$ より左、右にある今まで入れた星の個数をそれぞれ $L_i$ , $R_i$ とすると、どこまで入れるかで集合が一意に決まるので、この場合の数は $(L_i + 1)(R_i + 1)$ です。

左右の個数は BIT (fenwick tree) を使うと高速に求められれます。

59:34 に提出、AC。

G問題

問題文: G - Linear Inequation

KCPCメンバー(rated 参加)のAC数: 1/11

diff: 2157

Fはすんなり解けましたが、Eで時間をかけすぎているのでこのままだと負けです。

$dp_{i}[j] = 「\sum_{k=1}^{i} A_{k} x_{k} = j となる x の場合の数」$

とすると答えは $\sum_{i=0}^{M} dp_{N}[i]$ で求められますが、$M$ が大きすぎます。

とりあえずこのDPがFPSで表せられないかと考えてみると、

$dp_{i+1}(x) = dp_{i}(x)\sum_{k=0}^{\infty} x^{kA_i}$

とできました。

つまり、求める答えは

$[x^{M}]\dfrac{1}{1-x} \prod_{i=1}^{N} \dfrac{1}{1-x^{A_i}}$

です。

結局 $M$ が大きすぎます。

これを計算する方法を色々考えましたがわからず時間切れ・・・

終わってTwitterを見ると bostan-mori !!!

線形漸化式を解くアルゴリズムとして記憶にはありましたが、理解を全然していませんでした・・・

確かに次数が小さいのでこの形は bostan-mori で解けます。

基本的な理解が浅すぎました。

コンテスト結果・感想

6完(59:34)で 630位、1752パフォ、レートは19971975でした😭

スイングバイ成功ならず・・・

アンレ参加のABCでも最近は調子が良かったですが黄色復帰できませんでした・・・

まだABC1発入黄圏内なので、次のABCでは勝ちたい・・・!!!(勝ちましたが黄色復帰までは届きませんでした・・・)

ICPC 国内予選&Yokohama Regional 2025参加記 (チームbogosort : jastaway視点)

はじめに

京都大学チームbogosortのjastawayです。

ICPC Asia Yokohama Regional 2025が終わったので国内予選とYokohamaの参加記を書きました。

結果から言うと国内予選を12位(学内3位)で通過し、Yokohamaは5位でplayoffの参加権を獲得しました!

国内予選2025順位表
Yokohama Regional順位表

国内予選から時間が経っているので記憶が曖昧なところもあります。

長い参加記となってしまったので、時間のあるときにお読みください。

目次

チームメンバー

(レートはAtCoderのhighest)

  • soryuusi0219(B4, 2560)

チームのエース。特に数え上げが得意。今年の学生最強コンで5位とかいうバケモノ。最近さらにレートを上げてる。

  • tatesoto(B4, 1474)

ライブラリ整備の担当。コンテストでは写経とテストケース作成、幾何実装のサポートなどを担当。幾何とパズル系が得意。

  • jastaway(B4, 2042)

筆者。青黄反復勢。国内予選とYokohamaともに青色だった。簡単枠とやるだけ重実装の担当。比較的グラフ問題が得意?

背景

チームbogosortは去年結成しました。

tatesotoとjastawayが同学科でICPC出よーとなっていたところに、競プロをしばらく休んでいたsoryuusiを私が引っ張ってきてチームができました。(競プロ関係なく関わりがあったので)

名前の由来は各々が好きなアルゴリズムを上げたところ、tatesotoのbogosortが採用されました。

去年のICPCは国内予選6完16位(学内4位)と健闘しましたが、同大学にObjective-KUB1、Red Phobia、THSの鬼強3チームがいたので同校制限で惜しくも予選通過ならず。

今年はKUB1がWF権を使い果たし1枠空いたので去年と同じ感じだとYokohamaに行けるということで、予選通過を目標(ワンチャン台湾いけるか?)という感じでした。

国内予選まで

去年の一番の敗因はKUB1がいたことですが、圧倒的に練習量が足りていないと感じていました。

過去問もほとんどできていなかったのでとにかく練習をしました。

5月終わりから練習を始めて6月は週に2~3回対面のチーム練をしました。

去年全然していなかったので過去問は大量にあります。

国内予選の役割としてはA、Bはtatesotoが担当。他の問題はjastawayがすぐにできるときはして、あとはsoryuusiが担当するという感じです。

去年は、soryuusiがJIS配列派でUS配列派のjastawayはあまり実装をしないという戦略だったのですが、ソフトウェアの切り替えでJIS配列でUS配列の入力をするということができたので、今年はjastawayも実装に加わることにしました。これでsoryuusiをより考察に回すことができます。

国内予選はライブラリのデータをいくらでも持ち込めるので、練習と並行してライブラリの整備もしました。

tatesotoが主に幾何ライブラリの整備をして、だいぶ強いものができたと思っています。

soryuusiがFPSのライブラリを持っていたので、これらが使えるとだいぶ有利に働くと思っていました。

また、あまり活躍はしないかもしれませんが、OEISのデータをすべてダウンロードして検索もできるようにしました。

国内予選の前の週に行われたJAGの模擬国内予選にもチームで参加しました。

多くの京大チームが付属図書館のラーニングコモンズから参加していました。

jastawayはDのインタラクティブとEの構文解析、Fの文字列dpを担当し、Fは解けず、DEで非本質的なペナを大量に出してしまいました・・・(出力形式の間違い、保存のし忘れなど・・・)

soryuusiが強く7完8位(現役5位、学内3位)と結果は良かったです。

国内予選

tatesotoとjastawayは国内予選の直前に授業があったのですが、途中退出の許可をもらって会場に行きます。

前日に環境のセットアップやライブラリ、OEISのダウンロードなどを済ましておいたのであとは心の準備だけです。

DaiMongeとTHSにはほぼ勝てないだろうと予想していたので、できれば10位以内、少なくとも学内3位の座を目指します。

bogosortは橙コーダーがいますが決して安心はできません。

京大は選手層が厚く、Informatical_KU_B-treeやKUdosなどに全然刺されます。

コンテスト開始

開始直後は問題文の印刷が来ないので全員でA問題を見ます。

soryuusiが $O(1)$ の式を言うのでtatesotoが実装してAC(0:02)

B問題を見て制約が小さいので何をしても間に合います。

ここらへんで印刷が順番に来たので、実装をtatesotoにまかせてjastawayとsoryuusiが以降の問題を考えます。

tatesotoが実装を終えましたがサンプルが合わないのでjastawayがデバッグ作業に参加し、修正できたので提出します。AC(0:10)

この間にsoryuusiがCの方針を立てていたのでjastawayがそのまま実装します。AC(0:14)

さらにこの間にsoryuusiがDの方針を立てていたのでjastawayが実装しますが、サンプルが合いません。

少しケースが漏れていたそうで、そこを修正すると通りました。AC(0:28)

Eの考察もsoryuusiができていたそうなので、jastawayに伝えるよりも自分で実装したほうが早いということでPCを変わります。

その間にjastawayはtatesotoから問題を聞いてF問題の構築を考えます。

Eは考察が間違っていたそうで沼っていましたが1ペナの後、なんとか修正してくれて通りました。AC(1:16)

Fはjastawayが考察中で、Gはsoryuusiが3D幾何に見せかけた幾何ではないと見抜き、そのまま実装をしてくれました。AC(1:34)

Fの解法が立っていたので、tatesotoに共有しあっていそうということでjastawayが実装をします。

同じ文字のグループで3グループ目までは干渉できるので、ランレングス圧縮して残り3グループになるまで操作し、後ろから決めていけるというような解法です。

実装方針があまり良くなかったのか、とても時間がかかりました。

サンプルがあったので提出してみますがTLEが返ってきます。(どのタイミングで提出したか忘れました)

そうこうしてる間にsoryuusiがHを解けたと言うのでPCを渡します。そのままAC(2:02)

そのまま、I問題も解けたと言うので実装を続けてもらいます。

この間、jastawayはF問題のコードを印刷してデバッグをしています。

アルゴリズム的にTLEはありえないので、実装ミスでどこかのwhileループが終わってません。

tatesotoと一緒にwhile文を一つ一つ確認しながらバグを探しますがわかりません。

どこや〜と言っている間になんとI問題をsoryuusiが通しました。AC(2:24)

I問題を通した瞬間、同じ部屋のすでに全完しているDaiMongeから「そっち!?!?」と声が聞こえたそうです。(私はFの実装に集中してて聞こえませんでした)

あとは自分のF問題だけです。

色々なケースを試しても終わらないものがなかったですが、tatesotoが最大ケースを試してみようというので、デカいケースを作って入力してみると無限ループしました。

あとはいろんなところにデバッグ出力を書いて原因を特定します。

一箇所breakを書き忘れているところを発見し、サンプルも通ったので提出します。AC(2:31)

国内予選全完です!!!

この時点で6位でしたが、EFが遅く、どんどん抜かれていきます。

他の京大チームの兼ね合いからも学内3位は確定していて予選通過は確実となりました。

全完後は順位表を眺めながら、THSとInformatical_KU_B-treeがともに8完でTHSが落ちる可能性もあり注目していましたが、THSが終了1分前に全完し激アツでした。

結果

国内予選 replay

中盤は沈んでいましたが、立て続けにsoryuusiが問題を解いてくれてなんとか全完することができました。

12位と10位以内に入ることはできませんでしたが、去年の雪辱を果たし予選を通過することができました。

Yokohama Regionalまで

国内予選を通過し、次はYokohama Regionalに向けて練習をしました。

その前に、全員B4なので院試勉強をしなければなりません。

国内予選に全振りしててあまり勉強できてなかったので、しばらくはチーム練はお休み。

無事全員院試に合格し、初練習は9月に入ってからでした。

チーム練は基本的にAOJにあるJAGやYokohamaの過去問を週1で走りました。(詳しい練習の結果はAOJで見れます)

YokohamaはキーボードがUS配列固定なので、soryuusiには矯正してもらいました。

コーチのなにわづさんや他の常連チームに聞くとucupはいい練習になるとのことだったので、10月に入ってからはbogosortでucupにも参加しました。

11月はさらに詰めようということで、基本的に全員の都合が合う日はチーム練をしようということで2日に1回のペースで5時間コンを走りました。

この期間に限って言えば他のどのチームより練習をしている自信があります。ある週は1週間毎日やりました。(1日だけsoryuusiとjastawayのみ)

今年は問題順が完全にランダムということで、過去問(ほとんどがAB簡単枠)も問題順をシャッフルして練習しました。(GeminiにAOJのバチャの問題順をシャッフルするユーザスクリプトを書かせた)

AOJ シャッフルスクリプト

また、順位表情報も重要だろうということで、ICPC replayやJAGの順位表の情報から(シャッフルした問題順の)順位表を再生するwebアプリも(Geminiが)作成し活用しました。

ICPC Scoreboard Replayer

今年の夏合宿の問題や一部のregionalの問題はAOJ(や他のコンテストサイト)に上がっていなかったのでローカルでDOMjudgeを立てて練習しました。

ここでDOMjudgeに慣れれたのはいい練習になったと思います。

bogosortの戦略は、まずjastawayがセットアップとテンプレートを書きます。

その間に2人が問題を両端から読んでいき、簡単枠が見つかるとjastawayが実装します。

その後は解法を伝えるのが簡単な場合はjastawayが実装し、soryuusiが実装したほうが早い場合はそのまま実装してもらいます。

tatesotoは問題を読み終えた後、構築などのパズル系や幾何に張り付いたり、問題概要のメモやテストケースの図示、作成をしたり、写経をしたりします。

練習を重ねた所感としては、うちのチームはすべての問題が噛み合って他のすべてのチームが下振れれば優勝があり得るくらいで、うまく行けば10位以内に入れるだろうという感じでした。

うちの強みはsoryuusiの数え上げとtatesotoの幾何です。

本番の正解数が少ないこれらの問題が通せて、他の可能枠が通せずギリ勝ち、ギリ負けということが良くありました。

可能枠で沼らず、さらに難しい問題が解ければ最上位の集団に加われそうです。

ライブラリもtatesotoが整備してくれて、短い実装のものをたくさん用意してくれました。

写経に適したメインライブラリを29ページ、国内予選にも持ち込んだ写経を想定していないサブライブラリを60ページほど用意しました。

直前の12/3(金)に最後のチーム練をしましたが、過去一の大炎上をし大負けして幸先が悪いです。

Day1

朝5時前に起床しました。soryuusiは3時台に起きていたそうです🤔

コーチのなにわづさんと京都駅で合流し横浜へ向かいました。途中でTHSとも合流

お昼はHuaweiの方から食事会にお誘いいただいたので、高級そうな京料理をいただきました。東大と阪大のチームもいました。

無事会場に着き、Registrationをしました。

リハーサルではVS Codiumの設定の確認や写経の練習、開始直後の動きを確認しました。

スタッフとのやり取りはすべて英語でしたがパッションでなんとかなるので、あまり不自由はしませんでした。

1日目のイベントが終わり、東大+京大のチームで中華街に晩御飯に行きました。

人数が多かったのでMagical Fish + DaiMonge + bogosortで店に入りました。思ったより安くて美味しかったです。

ホテルに帰ってきて私はABCに参加しました。

先週のARCで落青したのでなぜかRatedです・・・

A~Fまでをすんなり解くことができてギリ黄色復帰できそうかと思っていましたが、1997でギリ復帰ならず・・・

G問題は区間制約がなかったらdp高速化ができてたのですが、区間制約を組み込むのができませんでした・・・

soryuusiはすぐ寝てて、tatesotoは最後のライブラリ整備をしてくれてました。

Day2

6時に起床し、ホテルの朝ごはんを食べに行きます。ビュッフェで種類も多く美味しかったです。

前日にtatesotoが整備してくれたライブラリなどを印刷しDaiMongeと会場に向かいます。

ちなみに、bogosortのdiscordサーバーには"戒め"チャンネルがあり、練習などでやらかしたことを書き込んでいます。これも印刷しました。

例えば以下のようなことが書いています。

  • bitshiftは非負
  • $x=r\cos\theta, y=r\cos\theta$ としない
  • デバッグ用にMAXを変えたら提出前に戻す
  • unorderedのclearは時間がかかるので注意

コンテスト本番

戦略通りjastawayが環境構築をし、その間に両端から2人が問題を読みます。

開始6分でDのFAが出たのでDをsoryuusiが読み、すぐ解法を言ってくれたのでjastawayが実装します。

__int128_tが要求されて出力が少し手間でしたがAC(0:11)

soryuusiがCの解法を思いついたそうなのでPCを変わります。

その間Eが通されているのでsoryuusiに問題を伝えると辺の長さをにぶたんすればいいと言われました。

有理数で出力する必要があるというと、答えは $a/n, b/n, c/n$ の形式になるから $n$ をにぶたんすればいいと言われ、soryuusiが実装方法も思いついてそうなので、実装してもらいます。AC(0:38)

soryuusiにCの続きを書いてもらっている間にjastawayは次に通されているHを考えます。

$k$ が $5$ 以上であることから、面を覆えないので端に着目すると確定できそうというお気持ちが生えました。

最も上の最も左の#について被覆できるならUの回転の4パターンしかなく、その時に1辺は必ず空白があるというのが多分証明できたので、貪欲に除去できそうという解法が立ちました。

PCを変わってもらい、Hの実装をします。

#の座標をsetに入れて4辺を調べて消すことを実装します。

コードが書けてサンプルがあったので提出します。

WA・・・

tatesotoに実装を説明しながら一緒にデバッグします。

説明しているうちにミスに気づいたので修正して提出します。AC(1:07)

soryuusiがCの実装をしますが、解法に穴があったそうです。

soryuusiがJの解法を思いついたそうなので、それを聞きます。

場合分けが多く、実装も大変そうなので、jastawayが実装を巻き取ります。

バグ取りを含めて30分はかかると伝えます。

jastawayがJを実装している間にsoryuusiがIを解けたらしいのでPCを変わります。AC(1:33)

Jの実装に戻ります。

その後soryuusiがGを解けたらしいので、tatesotoにmodintの写経をしてもらいます。

写経が終わると印刷し、紙でライブラリと比較してミスを探します。soryuusiには実装を進めてもらいます。

この動きは練習の時から決めていました。

軽微なミスを見つけて直します。

soryuusiからmodintがおかしいと指摘を受けたので全員でチェックすると、写経ミスが見つかったので修正。

そのままGを通してくれました。AC(2:12)

Jの続きを書いていると、soryuusiがCの修正ができたと言います。

Jの実装がとても長く、絶対にバグがある自信があったので、先にCをやってもらいます。

なかなかサンプルが合わず、細かい修正の末なんとかCが通りました。AC(2:55)

後解けそう(と思っていた)なのはJ、A、Fです。

Jの実装が終えてサンプルを試して、少し修正をするとサンプルが合いました。

まだバグらせている自信があったので、tatesotoに作ってもらってたケースを試すと落ちました。

原因を特定し修正して投げます。

WA・・・

ランテスを書くことも考えますが、まずはいろんなケースを試してみます。

tatesotoがテストケースをいろいろ考えてくれて試すと、一つ落ちるケースが見つかりました。

その部分を修正してもう一度投げます。AC(3:35)

soryuusiがAが大量の状態を持つdpでできそうと言いますが、16*5通りくらいの場合分けが必要らしく、soryuusiが実装している間にjastawayが一部の遷移を考えます。

soryuusiが場合分けが減る解法を思いついたとのことで、jastawayはtatesotoとFを考えます。

適当に座標を決めていくと $2n$ 回の質問で原点を含めて同一平面上にない4点を選ぶことができ(ない場合はこの時点で座標が決まる)、そこから $n$ 回の質問ですべての座標を求めることができると考えました。

各フェーズで必要な座標計算をtatesotoに投げます。

このあたりでAの実装が終わったようでサンプルや適当なケースが合ったので出しました。

WA・・・

jastawayがテストケース作りをします。

soryuusiとコミュニケーションをしているとsoryuusiが誤読していたことに気づきました。

残り40分くらいだったと思います。

結構ヤバいと思いましたが、逆になんとかなるそうです。

遷移を考えてもらっている間にFの座標計算以外の部分をjastawayが実装します。

3つ目の点を選ぶところくらいまで実装し終えたときに、soryuusiができたというので変わります。

Fは実装し終えても通し切る自信がなかったので順位表的にもAを通すことを優先しました。

残り20分もなかったと思いますが、頑張ってもらいます。

実装は結構すぐに終わったっぽいですが、サンプルが合いません。

書いていた遷移表をにらめっこしてミスを探します。

サンプルが合ったので提出します(残り8分くらい)

WA・・・

通りません😭😭😭

遷移を確認しますが、少なくとも紙の表とは合ってそうです。

そのとき、soryuusiが何もないところをスキップするときに間違えてそうと気付き、修正します。

そこが問題になるケースがあまり思いつかず、色々ケースを試しますが合ってそうです。

もういくらペナしてもいいからとりあえず出そうと言い、提出します。AC!!!!(4:54)

終了6分前に通してくれました!!!天才!!!

ほぼほぼ諦めですが、jastawayがFの実装の続きを書きます。

残り1分で諦め、カウントダウンを聞きます。

コンテスト終了。

配られたお弁当を食べる時間はありませんでした・・・

Yes/No、懇親会

凍結解除前の順位表を見ると、bogosortは4位です!!!

DaiMongeとTHSの優勝はなさそうで、bogosortは10位以内には入ってそうです。

playoffへの進出はbogosort視点ではほぼ確実でした。

DaiMongeは7完+2提出、THSは6完+3提出で全部の提出が通っていたら抜かれてしまいます。

DaiMongeはGとLに提出しており、Gは絶対通ってるだろうと思い、L次第でした。

THSはFに提出があり、bogosortも解法は思いついてた(正誤は不明)ので通されてても不思議ではありません。

結果DaiMongeは2つとも通しており9完3位でした。さすがです。

bogosortは8完5位でTHSには勝つことができました。

THSもF以外はしっかり通しており、8完7位と京大勢は全員playoff進出です!!めでたい!!!

ICPC Yokohama replay

ずっと上位を張れてていい感じでした!

コンテスト中はチョコくらいしか食べておらず、非常にお腹が空いていたので懇親会でいっぱい食べました。

企業ブースに去年コーチをしてくださったMoririnさんがいたので挨拶をしに行ったり、judgeのhenoさんとお話をしたりしました。

henoさんにFの考察を伝えると、浮動小数でやると誤差で落とされるとのこと・・・

AよりFを優先しなくてよかった〜!!

キーボードが古くなったとかで配ってたのでUS配列キーボードをもらって帰りました。研究室に置いとこうかな。

会場を後にし、京大チーム一緒に京都に戻りました。

感想

Yokohama Regional初出場でしたが、良い成績を残せて良かったです。

想定では、時間では他のチームには勝てないから完数で勝つつもりだったのですが、序盤の動きが良く、大量のチーム練の成果が出たのかなと思います。

台湾でのplayoffは3月の頭にあるみたいなので、それまでに更に強くなって、他の京大チームに勝てるようにしたいです。

まだsoryuusiに頼り切りなところがあるので、負担を少しでも減らせるように個人の実力も付けていきたいところです。

まずは黄色復帰をしましょう。

長い参加記となってしまいましたが、読んでくださりありがとうございました。

playoffでも良い成績が残せるようがんばります。

P.S.

bogosort のteam introductionです(実行してみてくださいWandbox)

is_sortedのために#include <algorithm>が必要だったみたいです・・・

gccで-std=c++23をつけるとコンパイルできるみたい?

AtCoderのC++23(GCC 15.2.0)では動作確認できたので試してみてください!!!

   /*bogosort  */#include     <random>     //$     "*#
       //      KC            *(      ja    st a   w ay
       /*      */#include    <iostream>    //  & %  +=
       /*      PC            |1      1|    */  std  //
       ::      mt19937_64    R;      //    .$       #@
  
#include/*      */<vector>
/*       ])    ([        */     #define KU      begin(v),\
v.       /*    {$        !?    */       end    ()        //
#define/*,     */        AA    ;;       for    (I        i\
=0       ;i    <n        ;i    +=              7^        6)
/*       >)    (<        :P    */     using    //        :)
namespace       std;;using     I=       int    ;;        ;I
                                main(){I n      ;cin>>n;//
 vector<I>      v(n)AA cin
>>       v[    i]        ;;    while(9-8!=     is_sorted(KU
))             //        ta    te        so         to
 shuffle(KU    ,R        );    AA        /*         <?
         %^    ;+        */    {cout<<v[i]          <<
//       so    ry        uu    si      02           19
 (char)32;      }cout<<"\n     "[       0]          ;}
                               //        ()         KU

【ABC434】AでペナったうえにEは嘘解法疑惑??

こんにちは、kmmtkm です!今回はABC434 の参加記です!

ABC432は友人との北陸旅行に行っていたため、ABC433は院生として研究集会に参加していたため、それぞれお休みしてました。今回が3週間ぶりのrated 参加です。どちらも波乱が起きた回だったと聞いているので、ちょうどいいタイミングに被ってくれて良かったです。まだ問題見てないので、時間を見つけてバチャをしたいと思います。

目次

ABC434

最近は調子が良く、ABC429, 430で2連続青パフォを叩き出すことに成功していました!前回参加のABC431では負けてしまいましたが、それでも3週間でレート+100 というのは誇っていいことだと思ってます。

ABC434開始前のレートは1328 、このまま勢いづいて、水色3枚瓦にたどり着きたいところです。

A問題

問題文: A - Balloon Trip

KCPCメンバー(rated 参加)のAC数: 13/13

diff: 45

この手の問題は割り算が正攻法でしょうか。とはいえ境界値でやらかしやすいので、A問題くらいであればループで書くのが確実ですね。

ということで 1:25 に提出します。

A問題でWAだと...??
...WA!?!?!?

何ということでしょう、AtCoder 最高の瞬間をA問題でやってしまいました。。。

w, b = map(int, input().split())
w *= 1000

for n in range(10**5):
    if w < b * n:
        print(n)
        exit()

という感じで書いてたのですが、よく考えるとn の上限は$10^{5}-1$では足りてません。$A = 100, B = 1$のときに$10^{5}+1$が答えになってしまいます...

やけくそになって、ループの上限を$10^{8} + 10$にして再提出、ACとなりました。

雑にやりすぎたことを大いに反省しています...

B問題

問題文: B - Bird Watching

KCPCメンバー(rated 参加)のAC数: 13/13

diff: 109

鳥の種類の番号は1~100と小さいので、各種類について大きさを格納するリストのリストsizeを作ればOKです。求めるものは、各$k$に対して

sum(size[k]) / len(size[k])

となります。

4:20 にAC

C問題

問題文: C - Flapping Takahashi

KCPCメンバー(rated 参加)のAC数: 12/13

diff: 577

このあたりで空に関する問題が多いな~と思い始めます。5秒くらいして、Sky株式会社さんのコンテストだからか~と気づきました。岩井星人さんが気付いていたタイミングと同じくらいな気がします

この手の問題は、各時刻において存在可能な範囲を求めていくのが良いです。 問題文における「$i$個目の目標は時刻$t_{i}$時点での高度を$l_{i}$以上$u_{i}$以下にすること」を、「時刻$t_{i}$時点での存在可能な高度は$l_{i}$以上$u_{i}$以下である」と読み替えておきます。

高橋君が時刻$t$に高度$h$にいる場合、高橋君が時刻$t+dt$において存在可能な高度は$(0, \infty) \cap [h-dt, h+dt]$です。よって、高橋君が時刻$t$に高度$[l, u]$に存在可能な場合、高橋君が時刻$t+dt$において存在可能な高度は$(0, \infty) \cap [l-dt, u+dt]$です。

このことから、時刻$t_{i}$に高橋君が存在可能な高度の下限$L_{i}$と上限$U_{i}$が、以下の式で求まります。

$$L_{0} = U_{0} = H, L_{i+1} = \max\{L_{i} - (t_{i+1} - t_{i}), l_{i})\}, U_{i+1} = \min\{U_{i} + (t_{i+1} - t_{i}), u_{i}\}.$$

この式を計算した後に、$L_{i} > U_{i}$となる$i$が存在すれば答えはNo、そうでなければYesです。

12:07にAC

D問題

問題文: D - Clouds

KCPCメンバー(rated 参加)のAC数: 8/13

diff: 1040

2次元Imosか何かをしたくなる見た目をしています。とりあえず、各マスが何個の雲で覆われているかは2次元Imos法で求まります。ここまで思いついて、とりあえず自分のライブラリにある2次元Imos法のコードをコピーしました。しかしペーストする前に思います。雲$k$だけで覆われているかどうかってどうやって判定するんだ...???

経験上、闇雲に特攻すると碌なことになりません。一旦冷静になって方針を立てます。

全部一気にやるのは大変そうなので、一歩ずつ落ち着いて考えたいところです。先に述べたように、各マスが何個の雲で覆われているかは2次元Imos法で求まります。特に大事なのは、覆う雲の数が0個のマスと1個のマスです。というのは、

  • 覆う雲の数が0個のマスは、各$k$について答えに計上される
  • 覆う雲の数が1個のマスは、そのマスを覆う雲について、その雲を取り除いたときにのみ答えに計上される
  • 覆う雲の数が2個以上のマスは、各$k$について答えに計上されない

からです。

ここで、次のことに気付きます。

  • 雲$k$を取り除くことによってどの雲にも覆われなくなるマスとは、長方形領域$[U_{k}, D_{k}]\times[L_{k} \times R_{k}]$にあるマスで、ちょうど1つの雲にだけ覆われているマスのことである

よって、 $$f(r, c) := \begin{cases} 1 & \text{if マス} (r, c) \text{ はちょうど1つの雲に覆われている} \\ 0 & \text{else} \end{cases} $$ とし、どの雲にも覆われていないマスの数を$Z$としたとき、各$k$に対する答えは

$$Z + \sum_{r=U_{k}}^{D_{k}}\sum_{c=L_{k}}^{R_{k}}f(r, c)$$

となります。

各マスを覆う雲の数は、2次元Imos法を用いることで$O(N + HW)$で求まります。また$\sum_{r=U_{k}}^{D_{k}}\sum_{c=L_{k}}^{R_{k}}f(r, c)$については、2次元累積和を用いると、$O(HW)$の前計算の下で$O(1)$で求まります。よって全体の時間計算量は$O(N + HW)$となり、十分高速です。

27:23にACとなりました。

D問題AC時点で351位!

この時点で351位、Aのペナで多少抜かされそうですが非常に順調です。

E問題

問題文: E - Distribute Bunnies

KCPCメンバー(rated 参加)のAC数: 8/13

diff: 1389

貪欲法で行けそうな雰囲気を感じ取ります。とりあえず、最終的にウサギが存在しうる座標の候補は$C := \bigcup_{i=1}^{N}\{X_{i}-R_{i}, X_{i}+R_{i}\}$ の高々$2N$個です。この$C$の中に、ちょうど一つのウサギが存在しうる座標があれば、その座標は順次採用していくことが可能です。

これを基に、以下のような貪欲法を書いてみました。

  1. 各$c \in C$について、$c$に到達可能なウサギの番号のリストを作成する。
  2. 1で作成したリストの要素数が小さい順に以下のことをする。
    • 既に使ったウサギの番号があれば、その番号をリストから削除する
    • リストに要素が残っていれば、答えに1を足す
    • リストの先頭の要素を、使ったウサギの番号として記録する

各$c\in C$に対して、1で作成したリストの大きさが1であればこの解法は正当ですが、そうでない場合には結構不安を感じます。特に、リストの先頭の要素を記録している点に問題がありそうな気はします。

しかし、この時点ではAC数が少なかったことと、サンプルが通ったことから、出してACすれば儲けもの、WAだとしても傷は浅いと踏んで、一旦提出してみます。

予感通りWAと言われてしまいました...

とはいえ、ACが17、WAが25なので、極端に悪い解法ではなさそうです。一つ一つ問題点を洗い出すことで何とかしていきたいところです。

まずは、今の解法で落ちるケースを探してみます。上で述べた貪欲法を丁寧にデバッグしていくと、サンプル2でインデックスを適当に入れ替えたものでWAとなることが分かりました。やはり同じ座標に複数のウサギが存在しうるケースを丁寧に扱う必要がありそうです。先程書いた貪欲法をベースに、ここを改善していきます。

いろいろ手を動かしていると、以下のことに気付くことができました。

  1. 1匹のウサギしか存在しえない座標については、そのウサギがその座標にいるものとして損しない
  2. ウサギ$i$の座標を$X_{i} + R_{i}$に決定したとすると、
    • ウサギ$i$は$X_{i} - R_{i}$に存在することはできない
      • 特に、座標$X_{i} - R_{i}$に存在できるウサギの番号リストから、$i$を削除しないといけない
    • 座標$X_{i} + R_{i}$に存在可能なウサギ$j$について、ウサギ$j$は座標$X_{i} + R_{i}$に配置しないものとして損がない
      • つまり、$X_{j} + R_{j} = X_{i} + R_{i}$ならば、ウサギ$j$は座標$X_{j} - R_{j}$にいるものとして損しない
      • $X_{j} - R_{j} = X_{i} + R_{i}$ の場合も同様
    • ウサギ$i$の座標を$X_{i} - R_{i}$とした場合も同様
  3. 全ての座標に2匹以上のウサギが存在しうる状態では、存在しうるウサギの数が少ない座標から決めていって問題なさそう
    • ここは未証明

ということで、上記のことを良い感じに実装していきます。各ウサギが存在しうる座標については、座標圧縮により0以上$\# C$未満になっているものとします。すると次のように実装可能です。

  1. $c_{i}$を、座標$i$に存在しうるウサギの数とする。
    • ただし、座標$i$に存在できるウサギの数が0のとき、$c_{i} := \infty$とする
  2. $i=0, \dots, \#C-1$について、$(i, c_{i})$を要素とする1点更新区間minのセグ木を作る
    • 大小関係は、$c_{i}$と$c_{j}$の大小関係で定義する
  3. $i=0, \dots, \#C-1$について、座標$i$に存在しうるウサギの番号の集合$s_{i}$を作る
    • $\# s_{i} = c_{i}$に注意
  4. セグ木全体での最小値$(k_{\min}, c_{k_{\min}})$について、$c_{k_{\min}} \ne \infty$である限り、以下のことを繰り返す。
    • 答えに1を足す
    • $s_{k_{\min}}$ から適当に要素$x$を取り出し、削除する。
    • ウサギ$x$が存在しうる座標$a \ne k_{\min}$について、$s_{a}$から$x$を(存在すれば)削除する
    • セグ木の$a$番目の要素を更新する
    • $s_{k_{\min}}$の要素が存在する限り、以下のことを繰り返す
      • $y \in s_{k_{\min}}$を取り出し、$y$を$s_{k_{\min}}$から削除する
      • ウサギ$y$が存在しうる座標$b \ne k_{\min}$について、
        • $y \in s_{b}$であれば答えに1を加える。その後、$s_{b}$を空集合に更新し、セグ木の$b$番目の要素も更新する。
        • $y \notin s_{b}$であれば何もしない

実装しているといろいろとこんがらがり、その上変数名の付け方が良くなかったこともあり、結構バタついてしまいました。しかし何とか実装しきり、サンプルが合い提出したところ、ACすることができました。時間は89:27、D問題のACから1時間以上経ってのことでした。

コンテスト後

解説を読んでいると、E問題の解説にはいきなり次のようなことが書かれていました。

貪欲法や DP などのアルゴリズムではどうやっても上手くいきません。

私は貪欲で書いたつもりなのだが!?!?と混乱しました。嘘解法だったのか...?? 弊サークル代表のみうねさんも貪欲法で通したと言っており、余計に???となっていました。

そんなところで、しもりんさんの以下のツイートを発見しました

どうやら上記の解法は公式解説の解法1と本質的には同等のようです。嘘解法になってなくてよかった...

しもりんさんありがとうございます!

コンテスト結果

Highest更新!

結果は1009位、1466パフォで、レートは13281342でした。何はともあれHighest更新です!!

ペナはもったいなかったですが、しっかり5完して勝てたのは良かったのではないでしょうか。 修士の間に青コーダーという目標はまだ諦めていません。頑張るぞーー!!!

【ABC433】これは......何?

お久しぶりです、柑橘です。寒くなってきましたね、皆様いかがお過ごしでしょうか? 自分はいい加減冬の間にレートを水色にしたいのですが、今のところ精進不足で全然なれそうにありません。かなしいなぁ。
さて、ABC433ですが、自分はこの日の夜用事があってUnratedで参加しました。問題を見るのは現在(11/23,Sun)が初めてなのですが、果たしてどんな問題なんでしょうか。
ん?

【推定difficulty】
A: 43
B: 47
C: 259
D: 752
E: 1674
F: 1617
G: 2060

……うわぁ。

以下、実際解いてみた際の感想などを記していきます。Unratedで良かったかもしんない……。

ABC433

配点: 100-200-300-400-450-500-600

A問題

問題文: A - Happy Birthday! 4
提出コード: Submission #71182166 - AtCoder Beginner Contest 433
まぁやるだけです。一瞬方程式を立てたくなりますが、方程式を立てないことが競プロなのだと最近気づきました。
cout << "Yes" << endl などを数文字で出せるマクロを設定しました。便利だ! こうやって人のコードは徐々に可読性を失っていくのでしょうね。諸行無常

B問題

問題文: B - Nearest Taller
提出コード: Submission #71182306 - AtCoder Beginner Contest 433
やるだけ2。問題文の「左」を「右」に誤読して一瞬ヤバかったです。日本語、大事。
今回は制約が小さいので二重ループを回すだけで終了しました。提出コード内では$i$を$0$→$N-1$の順に見ているのに対し、$j$は$i-1$→$0$の順に見ていて、これで答えがすぐ出ます。$N$の制約が大きい場合は累積minとかで管理することになるのでしょうか。でもindexも持たないといけないから、pairで云々して……多分、どうにか、なるのでは……?(曖昧)
そういえば最近変数を小文字で宣言するようにしています。Shiftを押すの、案外手間だったりするので。

C問題

問題文: https://atcoder.jp/contests/abc433/tasks/abc433_c:tile
提出コード: Submission #71189906 - AtCoder Beginner Contest 433
むずくない???? 方針を立てるのにかなり難渋しました。
割と初期に気付くこととして、$S[i]$を頭文字とする部分文字列で1122列であるものは高々1個しか存在しません。じゃあその存在をどうやって判定すんのよ、ということになりますが、具体的には
①$S[i]$がその後どこまで続くのか、即ち$S[i]=S[i+1]=...=S[j]$かつ$S[j]≠S[j+1]$なる$j$を求める
②$S[j+1]$がその後どこまで続くのか求める
の2つをすれば良さそうです。どうやって?
散々悩みましたが、隣接している要素の同一/非同一を管理するにはUnionfindが便利なことを思い出しました。①については$i≦j<n$なる$j$について、$j$の代表元と$i$の代表元がどこまで一致するのか二分探索して、②については$j+1$を含むグループのサイズを取得すれば良いです。Atcoder library、最高。

D問題

問題文: D - 183183
提出コード: Submission #71190296 - AtCoder Beginner Contest 433
やるだけ3。10の階乗を$ M $で割った剰余(これ二重表現か?)や各$i$について$A[i]$の桁数だったり$ A[i] mod M $だったりを持っておけば良さそうです。
あとは問題文における$ i $と$ j $のどちらを固定するかですが、$ j $を固定してしまった場合、$ A[j] $の桁数を$ k $として、$ A[i] \times 10 ^ k \equiv - A[j] (mod M) $ なる$ i $を探すことになり、これは逆元を探すことになって嫌です。皆さんは逆元の求め方、覚えていますか? 僕はaclのmodintを使い始めてから一切分からなくなりました。確かあれでしょ、ふぇるまーのしょうていりがうんぬんでかんぬんで……。
$ i $を固定しましょう。$ i $を固定した上で$ j $の桁数については1~10の範囲で探せばよく、$ num[k][r] = (k桁でMで割った余りがrとなるものの個数) $みたいな配列を持てばよさそうです。
いいわけあるか!!!!
そうですね、$ M $の上限が$ 10 ^ 9$なので、配列$ num $を二重配列として宣言すると爆発します。解決法としてはvector<unordered_map<int,int>>型で$ num $を宣言することが考えられそうです。実行時間に不安が残りますが、いざ提出すると1401secで通りました。c++、最高。
まぁ流石にこれが想定解なわけなかろうと思って解説を読んだら、マジで想定解だったらしくたまげました。嘘すぎる。

E問題

問題文: E - Max Matrix 2

いや分からんくてワロタ。笑ってる場合か!
こういう問題っていわゆる「構築」と呼ばれるジャンルに含まれるのでしょうか。さっぱり分からない挙句、適当にこねた解答を投げても黄ばんで轟沈したので(Submission #71192828 - AtCoder Beginner Contest 433)、解説を見ます。
あー。なるほど......?
数を$ N \times M $から降順に見ていく、数が配列$ X , Y $に含まれる/含まれないで場合分けするまでは正しい方針で、細部の処理が詰められていなかったようです。
以下、整理します。

(0)数を$ N \times M $から降順に見ていく( for(int i=N*M; i>=1; i--) )
(1)数$ i $が配列$ X $に含まれ、$ Y $にも含まれている場合、$ X[u]=i , Y[v]=i $なる$ u,v $について、
<1-a> $ A[u][v] $にまだ数が書き込まれていない場合は、$ A[u][v] =i $とする
<1-b> $ A[u][v] $に既に数が書き込まれている場合、明らかに不適 Noを出力
(2)数$ i $が配列$ X $に含まれるが、$ Y $には含まれない場合、$ X[u]=i $なる$ u $について、
<2-a> $ j>i $なる$ j $について、$ j $は既にテーブルAに記入済で、その位置を$ A[uj][vj] $とすると、$ A[u][vj] $に数が未記入ならそこに$ i $を書き込む。
<2-b> そのような$ j $が存在しない場合、即ち$ j > i$なる全ての$ j $について$ j $の存在するマスを含む行の全マスに数が書き込まれている場合、不適 Noを出力
(3)数$ i $が配列$ X $に含まれないが、$ Y $には含まれる場合、(2)の逆
(4)数$ i $が配列$ X,Y $のどちらにも含まれない場合、
<4-a> $ j>i, $ $ k>i $なる$ j,k $について、$ X[u]=j , Y[v]=k $として、$ A[u][v] $に数が未記入ならそこに$ i $を書き込む
<4-b> そのような$ j,k $が存在しない場合、即ち①$ j > i$なる全ての$ j $について$ j $の存在するマスを含む行の全マスに数が書き込まれていて、かつ②$ k > i $なる全ての$ k $について$ k $の存在するマスを含む列の全マスに数が書き込まれている場合、不適 Noを出力

さて、実装しましょう。明らかに$ ntox[i]=(X[u]=i なるuを返す) $のような配列は必要です。また、いま記した流れを見るに、今見ている数より前に書き込んだ数の行番号だったり列番号を持っていると<2-a>や<4-a>のような操作がやりやすそうです。これはqueueで管理すればよさそう。また、不適判定をするために、今数を書き込もうとしている行/列はもう埋まりきっていないか?という情報も欲しいところ。こっちは、例えば$ cntx[i]=(i行目のまだ書き込まれていない残りのマス数) $みたいな配列で管理できそうです。
なんか解答はもっと圧倒的にスマートですが、これで、行きます(強引)
……実装中……実装中……
……実装中……実装中……


100 years later


Submission #71293208 - AtCoder Beginner Contest 433
二度とやりません(逆ギレ)。公式解説をしっかり読むことにします。
あと、学びとして、沢山のテストケースがある問題にはvoid solve()関数で対応した方がいいみたいですね。forループ内に普通に処理を書いていると、Noを出力した際に勢いあまってreturn 0をしてしまい、WAしました。今更すぎるだろ。

感想

本当はコンビネーションの公式で一撃で求まるらしいF問題についても言及したかったのですが、E問題で自分の気力が消滅してしまいました。なんと、この記事を書き始めたのが11/23で、E問題を前にぐずったり拗ねたり頭を抱えているうちに、現在は11/29。ABC434約1時間前です。こういう重い実装、どうにか解けるように実力を付けていきたいですね。
ところで、前回記事を書いた際に問題名に関連した小説に触れていたので、今回も一冊紹介しようと思います。今回のC問題とF問題に登場した1122文字列とは、文字の左半分がn、右半分がn+1と、半分で分かれた文字列を指していました。というわけで今回紹介するのはこちら、『半分世界』です。
https://www.amazon.co.jp/dp/B08RB1R985/
ジャンルとしてはSF短編集。表題作は、文字通り家が「半分」で切れて人形の家の断面図的なアレみたいになっちゃったお家についての話。他にも、同姓同名の吉田大輔さんが一夜にして突如二万人近くに増殖してしまう「吉田同名」など、楽しい短編が収められています。未読の方は是非!
それではまた、次のABCも頑張っていきましょう。読んでいただきありがとうございました。

【ABC432】難易度大逆転で波乱の展開!!

こんにちは、はるはるです。 今回はABC432の参加記です。

ABC432

コンテスト前のレートは1399でした。 ABC427からなんと5連続でレートが上がっており、初参加者の内部レート変更も相まって瓦奪還にリーチをかけました。青パフォを取ればhighestも夢ではありません!

配点は100-200-350-425-450-525-575です。525は運がよくないと解けないのでEまで1時間くらいで解きたいな...と思っていました。

A問題

問題文:A - Permute to Maximize

推定diff: 12

KCPCメンバー(rated参加)のAC数: 9/9

すべての数字は1~9なので、A,B,Cを降順ソートしたのち連結すればよいです。

01:06に提出、AC

B問題

問題文:B - Permute to Minimize

推定diff: 48

KCPCメンバー(rated参加)のAC数: 9/9

0以外の一番小さい数字を先頭に持ってきて、それ以外の数字は昇順に並べるのが最適です。

04:04にAC

C問題

問題文:C - Candy Tribulation

推定diff: 654

KCPCメンバー(rated参加)のAC数: 9/9

これ、本当に難しかったです...

まず、総重量を決めたときに飴の配り方は一意に定まります。$O(N)$で計算する方法は、すぐには思いつかないもののありそうだったので、これを軸に考えます。

考察を進めると、全員が小さな飴を一つ以上持っていて全員の総重量が等しい場合、それらの小さな飴を大きな飴に置き換えることで、総重量が等しいままに大きな飴の個数を増やせることに気づきました。そのため、誰か一人は大きな飴しか持たないことになります。

これで、総重量の候補は高々$N$個となりましたが、まだ計算量を落とす必要があります。

もう少し考えてみると、$A_{i}$が最小の人以外が大きい飴のみをもらった場合、$A_{i}$が最小の人はその総重量を達成できないことに気づきました。

これらより、$A_{i}$が最小の人は必ず大きい飴しかもらわないことがわかります。

これで総重量が一意に定まったので、あとは他の人について総重量を等しくできるかの判定を行います。

一つ一つの$A_{i}$について独立に計算する方法は思いつかなかったので(余りに注目すればできそうではある)、$A$を昇順にソートして前回からの差分を考えることにしました。

$i=k$の人について大きな飴を$n$個、小さな飴を$ m $個渡すとき、$i=k+1$の人については、飴の個数を$A_{k+1}-A_{k}$個増やしつつ飴の総重量は等しくする必要があります。増やす飴の分は小さい飴だけを渡すとしてよいのでこの時点で総重量は$X(A_{k+1}-A_{k})$だけ増えるため、この分の重さを減らすために大きな飴を小さな飴にします。

大きな飴1つを小さな飴にすると総重量は$Y-X$だけ小さくなるので、上記と併せて、$\frac{X(A_{k+1}-A_{k})}{Y-X}$が整数かつ$n$以下であれば$i=k+1$の人までは達成可能であることがわかります!

こうして全員について飴を配れるか判定していきます。

33:08にAC

体感425点くらいでした。4400人も通しているのは謎です...

E問題

問題文:E - Clamp

推定diff: 1133

KCPCメンバー(rated参加)のAC数: 7/9

D問題は意味不明だったので飛ばしました。順位表も見ておいてよかったです。

まず、$l \geq r$の場合はすべての$i$について$max(l,min(r,A_{i}))$が$l$になるので、$lN$を出力すればよいです。以下では$l < r$について考えます。

$max(l,min(r,A_{i}))$のはたらきについて考えると、$A_{i}<l$のときはlを、$A_{i}>r$のときは$r$を返すことになります。

そのため、
$lmiman=A_{i}のうちlより小さいものの個数$,
$rtyouka=A_{i}のうちrより大きいものの個数$
とすると、
$\sum\limits_{1 \leq i \leq N} max(l,min(r,A_{i}))$
$=l \times lmiman+\sum\limits_{l \leq i \leq r}A_{i}+r \times rtyouka$ です。
これらは一点更新区間和取得のセグ木を2つ持つと求められます。(lower_boundが使えるmultisetとセグ木1本でもOK)

更新については両方のセグ木をうまいこと更新すればよいです。

58:50に提出、AC

コンテスト後

今回のコンテストは難易度逆転が激しく、各問題のdifficultyは
12-48-654-1745-1133-2273-1984
となっていました。順位表を見て解ける問題を解けたかが結果に直結していそうです。

結果は59分4完でパフォーマンスは1481、レート変化は13991407(+8)でした。
highestである1420にはわずかに届かなかったものの、瓦は無事3枚になり、勝ちは6連続になりました。好調です。

年内に自分が参加できるABCはあと4回なので、いい気分で年越しを迎えられるよう頑張ります!