ツバサの備忘録

主に備忘録代わりに精進記録を載せていくつもりです。

ABC120 D - Decayed Bridges

問題
提出コード

解法

全ての橋が崩れた状態では、どの島からも別の島へ行くことができません。
よって、不便さは_{N}C _{2}、すなわち N(N-1)/2となります。
ということで、ここから逆に橋を組み立てていき、不便さを後ろから求めていくことにします。
今現在の不便さがCで、次に(a,b)を繋ぐ橋をかけるとします。
既にaからbへ行くことができる場合、不便さが減ることはないので、aからbへ行くことができない場合を考えていきます。
a自身を含む、aから行くことができる島の個数をA、同様にbから行くことができる島の個数をBとします。
このとき、(a,b)に橋をかけることで、Aに含まれる任意の島から、Bに含まれる任意の島へ、新しくいくことができるようになるため、
不便さはABだけ減ります。
ということで、C-ABが、このときの不便さとなります。
そして、a自身を含み、aから行くことができる島の個数は、A+Bへ増えます。
あとはこれを繰り返し計算していけばよいのですが、このa自身を含み、aから行くことができる島の個数を計算するのに、自分はUnion-Find木を利用しました。
それぞれのグループの根の部分に、根自身を含み、根からいくことができる島の個数を記録しておくことで、高速に計算することができるようになります。
あとは、順番にシミュレーションをしていくことで、この問題のクエリにこたえることができるようになります。

感想

考察自体は結構スムーズに行けたのですが、実装に時間をかけました。ので、自分の中ではもう少しはやく解くことができた、と思っています。ですが、安定して400点問題をはやときできるようになってきている気がするので、うれしいです!

ABC120 C - Unification

問題
提出コード

解法

積まれているキューブの中に、赤と青のものが少なくとも1つずつ以上存在しているとき、必ずどこかに操作を行える部分が存在しています。
ので、結局行うことのできる操作回数は、
min(赤のキューブの個数,青のキューブの個数)
となるので、これを二倍したものが答えになります。

感想

すごい素直なC…だと思います。ただ、常に操作を行えるかどうか、の部分の気づき、および確認をするのが少し難しいかなと思います。

北大・日立新概念コンピューティングコンテスト2018

ということで、初めてマラソンに参加しました。
この記事では、自分が最終的に行ったことと、時系列順に行ったことの簡単な列挙をしていこうと思います。

問題

リンクはこちら
基本的には同じ問題になります。制約が違う部分だけピックアップすると、
A:

3 <= N <= 50
1 <= K <= 50
0 <= d_i <= N

B:

3 <= N <= 300
1 <= K <= 1000
0 <= d_i <= min(N,6)

C:

3 <= N <= 10
1 <= K <= 2^N
0 <= d_i <= 6   

といった具合です。
実際、ランキングを見てもわかるように、この部分が違うだけでかなり得点の取りやすさが異なります。

最終的に行ったこと

提出コード(C)
基本的にはA~Cでやっていることは同じです。ただ、一部分岐条件を問題によって切り替えています。
やることは主に2つです。1つめは、Twitter等で公開されていた、「変数がすべて1」のケースの得点のみを狙い、極限まで項数等を削る方法で、2つめが、厳密解を効率よく作成し、両方のケースで高得点を狙う方法です。

変数がすべて1のケースの得点を狙う方法

こちらは、以下のツイートの方法とほぼ同じです。

https://twitter.com/arimasenu/status/1099583866614931456

手順としては、次の通りです。

  1. 定数項および1次の項について、係数をそのまま割り振る

  2. 正負の項をまとめられるものについてはまとめる
    k_{1}ab - k_{2}abcという項が存在していた場合、(kは係数です)
    k_{1}ab(1-c) - (k_{2}-k_{1})abc
    k_{1}で項をまとめることができます(k_{1} \leqq k_{2}の場合です、逆でもやることはほぼ同じです)。そして、まとめた後の左側の項は、(1-c)という部分が負になることはありえない上、変数すべてが1のケースでこの項が0になるため、消去しても問題ありません。
    よって、残った(k_{1}-k_{2})abcという項についてのみ今後考慮していけばよいことになります。このようにして、どんどん項数を減らしていきます。

  3. 係数が負の項について、以下のように係数を割り振る
    -kabcという項があったとすると、こんな感じです。
    2k -ka - kb - kc
    つまりは、定数項に係数×(項数-1)を-1倍したもの、そしてそれぞれの変数に係数をそのまま割り振る、という形です。
    この操作を行うことで、abc全てが1であったときのみちょうど-kぴったりになり、それ以外の場合はそれを下回ることがありません。

  4. 係数が正の項について、係数を割り振る
    kabcという項があったときに、a,b,cの項に、うまく係数を割り振っていきます。この際、それぞれの項ができるだけ0に近づくように割り振りたいので、自分は山登り法を用いて調べることにしました。
    まず、それぞれの一次の項に均等に係数を割り振ります。その後、それぞれの係数を移動することにより、よりスコアが高くなるような場所がないかを探していきます。

  5. 係数が正の項が存在していたら、定数項にまとめる
    採点のうちの片方、ランダムケースでの得点を完全に捨てているため、捨てられる項は捨てていきます。

  6. 負の係数の最大値を調整する
    例えば、-kaという項が存在していた場合、新規変数を利用して、- \frac{ka}{2} - \frac{kaw}{2}
    とした方が得点が伸びるパターンがあります。
    係数の絶対値の最大値をXとし、それを超える係数kが存在していたらXk-Xに分ける、ということを繰り返していきます(k-XがそれでもなおXを超えていたら、新規変数をもう一つ作成していきます)。
    あとはこれを全ての項に対して行います。ただし、新規変数は共有して問題ないので、(k-1)/Xの最大値が、新規変数の個数になります。
    そして、最もスコアが高くなるようにXを選択したいのですが、(おおよそ)凸関数になっているので、三分探索である程度範囲を絞ってから、残った範囲をすべて調べ、スコアが最大となるXを見つけます。

ということで以上が方法の1つめになります。この方法は、複雑な式、つまり項数が多いものに対して適用します。

厳密解を作成する方法

まず、厳密解を作成する方法はいくつか存在します。
notさんという方のブログを見ていただければ、論文に載っている厳密解の作成方法が書いてあるのでそちらをご覧ください。

新概念コン2018 論文まとめ - notブログ

おそらく自分の解法はこのブログの一番最初に載っているものが近いかと思います(この論文だけよく知らないのでなんとも言えませんが…違っていたり、そもそもこの方法が世に出回っていたらごめんなさい)。
厳密解の作成方法ですが、次数が0~2の場合はそのまま、そして係数が負の場合は、上のブログに載っている方法を利用します(ただし、すこし手を加えているので、後で説明します)。

係数が正の3次以上の項についての厳密解の作成

まずは、次数が3のものについて考えます。係数は最後にかけるだけでよいので省略します。
abcという項があったとします。ここに、新規変数wを加えると、次のように表現できます。

  • ab + w(-a-b+c+1)

a,b,c,wそれぞれに0と1をあてはめてみると、これがabcを表現できていることがわかります。
では、4次以上の場合はどのようにすればよいのでしょうか。
abcd...という項が存在した場合、cd...の部分をまとめて1つの変数uと表現できれば、上の3次のケースに帰着させることができます。
ということで、まずは2次の項cdを、uと置き換えることができないかを調べればいいことになります(項が増えても、あらたに置き換えた変数と、もう1つの変数に対して同じ方法を適用していけば、最終的に1つの変数になります)
ということで結果は以下のような形になります。

  • cd + u(-c-d+1)

(c,d) = (0,1) or (1,0)の際にuの中身が0になるから動作するかどうかわからないじゃないか、と思うかもしれませんが、その場合でもu=0が最適になります。
なぜならば、仮にu=1とすると、元々の項abcdについて考えてみると、(a,b)=(1,1)の際に(a,b,u) = (1,1,1)となり、abuの項の係数が加算されてしまいます。現在、係数は正のパターンのみに限定しているので、(a,b,u) = (1,1,0)とした方が、abuの項の係数が加算されずに済むため、u=0が最適となるからです。
ということで、上の式を利用すると、uという変数が、新しくcdを表す変数になりました。ので、abcd...という項ではなく、abu...という項を表現する問題に帰着できます。
これを繰り返し、3次になった時に、先ほどの公式に当てはめれば、厳密にabcd...を表現できたことになります。
この方法の良い点は、項数が増えすぎない、という点だと思っています。
例えば、abcabdが存在した場合、
2ab + w(-2a-2b+c+d+2)
とやると、2つの項をいっぺんに表現できます。
また、abefという項とcdefという項の二つが存在した場合、
まず、abefefについて、
ef + u(-e-f+1)
と変形をしてあげてから、
ab + w(-a-b+u+1)
としてあげることで、abefを表現できますが、
ab + w(-a-b+u+1) + cd + x(-c-d+u+1) + 2ef + u(-2e-2f+2)
としてあげると、abefcdefの、efの部分がu一つで表現できます。
それだけでなく、
上の式におけるwの部分そのものも、実はabを表す項になっていて、
具体的には、abcという項と、abdeという項が存在した場合、
ab + w(-a-b+c+1)
abcを表現でき、
de + x(-d-e+w+1) + ab + w(-a-b+1)
abdeを表現できます。まとめて、
2ab + de + w(-2a-2b+c+2) + x(-d-e+w+1)
としてあげると、これだけで2つの項を表現してあげることができました。
あとは、どの2次変数に対して新規変数を作成するか、の最適解を探せばよく、今回はこちらは焼きなまし法と山登り法(焼きなまし法の条件がうまくかみ合った問題については焼きなまし法を使いました)で探すことにしました。

係数が負の項についての厳密解の作成

こちらは、基本的には上で紹介したnotさんのブログにも書かれている、論文の方針と同じになります。
-kabcという項を表現するには、
kw(-a-b-c+2)
とすればよいです。
新規変数wを1つ用意し、それぞれの変数とwからなる二次の項にはそのまま-kを、そしてwのみからなる一次の項には、k \times (変数の個数-1)を割り振ってあげればよいです。
ここで、-kが大きい場合、wのみからなる項の係数が大きくなってしまいます。
そこで、w,uという二つの変数を用意し、Xという基準を設けて、kXより大きかった場合に
Xw(-a-b-c+2) + (k-X)w(-a-b-c+2)
のように分けてあげることで、得点を伸ばせることがあります。
厳密解でない方法の、負の係数に対する処理とほぼ同じものになります。なので、これも三分探索をして、最もスコアが上がるXについて調べてあげます。

その他細かい手法

-kabcという項が存在して、
-kw(-a-b-c+2)という項を作成したときに、
jabcd (ただしj \leqq k )という項を埋め込むことができます。
-kw(-a-b-c+2)+jwd
としてやると、1つの項のみで、4次の項を1つ作成できたことになるのでお得です。
今回は、焼きなまし法等でじっくり時間を費やしたい(実装が難しくて面倒くさい)ので、一部の項のみ、これを適用しました。具体的には、負の係数がある項の変数と、それらの変数より大きい番号の変数1つからなる正の項のみに絞りました。
具体例を出すと、-kabcという項が存在したら、jabcdjabceのうちのどれか1つが対象になります。
正の係数に対する負が存在するかどうかは、負の係数の項を辞書順ソートしておくだけで、二分探索で調べることができるので、これなら時間がそこまでかかりません。

ということで、記載漏れがなければこれが自分の解法のすべてになります。
あとは、ランダムケースを捨て、全てが1の場合の得点を稼ぐ方法を項数が多いものに対して、厳密解を作成する方法を項数が少ないものに大して行えばよいです。


ということで、最終的な順位はわかりませんが、15個のテストケースに対する得点は次のようになりました。
f:id:emtubasa:20190227225538p:plain

感想

今回初参加だったのですが、最初のマラソンで2週間のものに出場するのはやめましょう
自分は、できるとこまでとことんやる性格(自称)なので、ほぼ2週間、隙あらばマラソンのことを考えていたため、他の作業が何もできませんでした…。毎日夜遅くまで考えていたため、特に得点が伸びない時期は明らかに体調が悪くなり、親や友人に心配されてしまいましたw
ただ、その分得点がぐんと伸びたときはとても面白いです。普段の競プロで高難易度を解いたり高パフォーマンスを出すのとは別の気持ちよさがありました。
あとは、競プロとは違って、普段強い方々といい勝負ができる可能性があって楽しかったです(でもやっぱり強い方々はマラソンでも強いのですが…)。
提出欄を見ればわかるように、上位の方々は基本提出回数が少ないので、わずかな思考時間であの点数を出していると考えると、いかにすごいかがわかると思います。
今回のマラソンでだいたいどんな感じかわかったので、次にマラソン出る際は、とりあえず数時間のものに出場してみようと思います…w
ずっと同じことを考えて詰まってしまってもあまりよくないので、思考時間を少なくしてリフレッシュするのも大事ですし、それこそ体調が悪くなってしまっては元も子もありません。このあたりも調整しつつ頑張ってみたいです!(この性格があまり向いていないのでしょうか…w)

追記(おまけ)

#HokudaiHitachiProcon2018というハッシュタグを見ればわかるのですが、嘘を混ぜ込んだ解法がほとんどっぽいですね…!!
それでも、上位勢は論理的な嘘(?)というか、他の出場者とは一味違う実装をしているっぽいので、やはり差がくっきりと出ているように見えます。
RE、WAを吐かずに入賞することを期待して数日間待っていようと思います!

3/19追記

グッズが届きましたー!うれしいですね

ABC119 D - Lazy Faith

問題
提出コード

解法

すごく簡単にいうと、それぞれのクエリに対して、解の候補が8通りしかありません。なので、調べればよいです。
今いる地点をXとしたときに、Xより左側にある寺(神社も同様)のうち、一番近いものを1つみつけてひっぱってきます。右側も同様です。
この操作で、寺が2つ、神社が2つひっぱってくることができました。
あとは、それぞれの寺と神社の組み合わせを試していけばよいです。

  • 寺と神社が同じ側に存在する場合
    どちらもXより右側、もしくは左側に存在する場合です。
    これは、Xと寺(神社)の距離のうち、大きい方が最短距離になります。

  • 寺と神社が逆側に存在する場合
    例えば、寺が左側、神社が右側にある場合です。
    これは、まずXと神社(寺)の距離が近い方へ行き、そこから、逆側へいけばよいです。
    (Xとの距離の小さい方+寺と神社の距離)
    になります。

    Xに最も近い寺と神社は、二分探索をうまく利用することで探すことができます。upper_boundを使ってXを調べると、見つかった場所が、Xより右側で最も近い場所、そしてその1つ前の要素が、左側で最も近い場所になります。
    あとは、これを使って4箇所の位置を特定し、上の4通りを調べ、一番距離が短かったものを出力、ということをクエリの回数だけ繰り返せば答えとなります。

    感想

    実装悩んでいる暇があったら書いてしまえ!ということで実装していたら、とてつもないスパゲッティコードができあがりました。もうすこしスピーディかつ綺麗に書きたいものですね。
    最初、クエリの問題なので、どうせ前計算かなんかだろう、と思っていたのですが、Xの制約的に前計算が難しそうであることがわかります。そして、座標1からXまで1周することすらかなわないので、二分探索がからんでいそう、となります。そして、よくよく考えれば、最も近いのはこの4通りしかなさそう、となるので、あとはこの4通りを調べるコードを書けばいい!となりました。
    考察自体は久々に瞬殺できた気がします。

ABC119 C - Synthetic Kadomatsu

問題
提出コード

解法

全探索をすればよいです。
Aに使う竹、Bに使う竹、Cに使う竹の3種類にまず分けます。
これは、bitで竹を表現して全探索するとやりやすいかと思います。
ビットの部分集合を列挙する方法はこちらをご覧ください。

techtipshoge.blogspot.com

さて、その後は、それぞれについてコストを計算します。Aについて計算してみます。
まず、10 \times (Aに使う竹の本数-1)のコストが必要です。
あとは、Aと(今使用する竹の長さの総和の差)の絶対値
を計算して、これらを足し合わせると、Aを作成するのにかかるコストが計算できます。
同様にB,Cについても行えば、その合計値の最小値を探すことで、答えを求めることができます。

感想

bitの部分集合を列挙する方法を探すのに数分かかりました(他の方とのトーク履歴に残っているので、いつもそこから探しています…w)。そして、誤読をしていて、全ての竹を使わないと勘違いしていたり、そもそもバグを埋め込みすぎていたりしたため、先にDに飛びました。
なかなか実装の重いCではないかなと思います…

ABC023 D - 射撃王

問題
提出コード

解法

最大値の最小値を求めよ、といえば!二分探索です、ということで二分探索をしていきます。
X点を達成できるかどうか、を二分探索で調べていきます。
ここでの達成できるか、はぴったりではなく、X点以上を達成できるかどうか、になります。最終的に最小値を二分探索で求めていくため、ぴったりの値が最後に残ることになるので、X点以上かどうか調べるだけでよいです。
ということで、X点以上かどうかを調べる方法が以下になります。
まず、h_{i} \gt  Xとなるiが存在したら不可能です。
あとは、Xを超えないような、i番目の得点の最大値になるような秒数t_{i}を求めます。
\frac{(X - h_{i} ) }{s_{i}}を計算すると、その秒数が求まります。
あとは、この値を昇順でソートしたときに、
 t_{i} \lt iとなるようなiが存在したら不可能、存在しなければ可能になります( iは0-indexedです)。
あとはこれをもとにして二分探索を行うことで、答えを求めることができます。

感想

夏休みぐらいのときに、先輩から「最大値の最小や最小の最大値を求める問題では二分探索がしたくなる」と教わったので、実際にその通りに実行してみたら、サクっと解けました。
加えて、最近の練習会で、半開区間を利用した二分探索の実装方法を教わったので、二分探索に関してはバグなく通すことができました(h_{i} \gt  Xとなるパターンを考慮しきれていなかったのでWA自体は出したのですが…)。
この手の問題をサクサク解けるようになると楽しいです!

ABC023 C - 収集王

問題
提出コード

解法

まずはi行目を選択すると固定して考えてみることにします。
i行目を選択した場合にアメがr_{i}個得られるとします。
同様に、j列目を選択した場合にc_{j}個得られるとします。
この時、K個のアメを得るには、r_{i} + c_{j} = Kを満たすものならばいいように見えるかもしれませんが、実は違います。
(i,j)にアメが存在していた場合、r_{i}およびc_{j}それぞれにそのアメがカウントされているため、実際はK-1個である可能性があるからです。
同様に、(i,j)にアメが存在していた場合で、実際に(i,j)を選択してK個得られるパターンというのは、r_{i} + c_{j}を計算するとK+1となります。
しかし、(i,j)にアメが存在していない場合は、r_{i} + c_{j} = Kとなるマスが、求めるマスになります。
(i,j)の種類はRCなので、全てを探索するわけにはいきませんが、r_{i} + c_{j} = Kとなるようなマスの個数を求めるのは、二分探索を活用することで求めることができます。よって、うまいこと工夫をして、最初に述べた2種類のコーナーケースを考慮しつつ、計算ができるようにしたいです。
そこで、以下のようにすればよいことになります。

  • r_{i} + c_{j} = Kとなるようなマスの個数を求める
    iを固定したとき、c_{j} = K - r_{i}となるようなjの個数がわかればよいです。これは、c_{j}を昇順にソートすることで、二分探索を利用すればK - r_{i}のとなっているc_{j}の個数を求めることができます(upper_boundとlower_boundを求め、前者から後者をひけばよいです)。これを全てのiについて行えばO(R log C)で計算できます。

  • N個のアメの座標(x,y)について、r_{x} + c_{y} = Kとなっているものが存在するかどうかを調べる
    存在していたら、上の計算から引いてあげる必要があります。

  • N個のアメの座標(x,y)について、r_{x} + c_{y} = K+1となっているものが存在するかどうかを調べる
    存在していたら、上の計算に足してあげる必要があります。

以上の計算をすることで、重複なく、求めたいマスの個数を数え上げることができます。

感想

最初、Rについて二分探索をしたときに、辻褄が合わないマスが出てくることはすぐにわかったのですが、そこをどうするかで悩みました。
このような都合の悪いケースが出てきたときに、他の解法を探すか、そのケースだけうまく辻褄を合わせる方法があるかを調べる2択が発生すると思うのですが、今回ははずれを引きました。
ここら辺の勘というか経験といいますか、どちらについて考えればいいかがわかれば、ぐっと考察時間が短くなるような気はするのですが…