2020年6月29日月曜日

Codeforces Round #628 (Div. 2)

 Dまでの四完でした。
 コンテスト中は、EやFはあまり考えずに寝てしまった……。

コンテストへのリンク
コンテスト後のツイート

E. Ehab's REAL Number Theory Problem

 解説AC。

 約数が7個以下ということで、

・素因数は高々二つ。

 に気付くのが重要。また、

・$x^2$を因数に持つとき、$A/x^2$を考えても同じなので、平方数を約数に持つならそれで割っておく。

 というのにも自力で気付けた。

 ここでグラフを考えるのはまあ自然だと思う。
 各A[i]を頂点とし、たとえば、6=2*3と、22=2*11のように、同じ素因数を持つもの同士に辺を繋ぐ。そして、最小のサイクルを検出すれば良い。

 ……という方針は自分で思いついたのだけど、それだと難しいと思う。これでも上手くやれば解けるかもしれないが、サイクル検出を効率よくやるのが難しい。

 公式解説では、素数を頂点にし、6があれば2と3に辺を結ぶ。7があれば1と7を結ぶ……というにグラフを構成している。これでも同じく、最小サイクル検出問題に帰着できる。

 さて、最小サイクル検出は普通にやると、頂点数の二乗くらい(この問題では、辺の数も頂点数*2くらいで抑えられるので)の計算量がかかってしまう(と、公式解説にはあった。確かに各頂点ごとにBFSすればそれくらいでできるけど……もっと良い方法はないの?)。が、ここで、$A[i]\leq 10^6$に着目。$10^3$上以上の二つの頂点が結ばれることはないため、サイクルの端点は$10^3$以下として良いので、それらからBFSすれば良い。
 これをふまえると、計算量が大きく減らせる。

 これでACできるのだけど、実際はその後の実装にも苦戦してしまった。途中で座標圧縮したりしたらTLEが取れたけど、もっと良い実装があるはず。

F. Ehab's Last Theorem

 解説AC。
 ついでに、最近出た類題もAC。この類題の方が簡単なので、これを先にやってから取り組んだのは良かった。

 公式解説には、DFS木を使うものと、次数を考えるものの二通りが載っており、前者はじゅぴろさんの日本語の解説もあります。
 ただ、私は後半の、サイクルがなかったときの独立集合の取り方がよく分かってません……。

 なので、後者の方法でやりました。

 次数が少ない順(heapqで管理)に点を取ってきて、独立集合に付け加えていきます。
 ある点を使ったら、その点の近傍の点は使えず、さらにそこから繋がっている点の次数を一つ減らす……というようにやっていく。
 ここで、最小の次数が$k-1(k = \lceil \sqrt{n}\  \rceil $とする)以上になったら方針転換、k以上のサイクルを検出する方針に切り替えます。

 なぜなら、残りの点の次数が全てk-1以上なら、必ずk以上の頂点のサイクルを含むからです。ちゃんとした証明ではないけれど、その理由を以下に書きます。

 k頂点のサイクルを含まない時、一番次数を増やせるのは、k-1頂点の完全クラフです。なのでk-1頂点の完全クラフを考えると、各頂点の次数はk-2なので、各頂点からもう一個辺が出ていないといけません。そこから出る点と点が結びつけば、k以上のサイクルになるのでOK。
 そうじゃないとすると、またk-1頂点の完全グラフに結び付かなくてはいけない。だが、他の頂点からはやはりもう一個辺が出ていないといけないので……と、以下その繰り返しになるため、k以上のサイクルが存在しないとダメですね。

 で。
 サイクルの検出はDFSでOK。

 DFSで使っていない頂点をたどっていった配列$a_1, a_2,...,a_n$をしたとき、その一番最後の頂点$a_n$と繋がっているような最も最初の頂点を$a_i$として、$a_i, a_{i+1}, ... , a_n$の個数がk以上になります。
 どの頂点の次数もk-1以上という強い条件なのでこれが成り立つはず。(成り立つはずだけど、証明はよく分かっていないかも……)
 


2020年6月25日木曜日

パナソニックプログラミングコンテスト2020

 ABC形式の企業コンテスト。Dまでの四完と、成績は今一つでしたが、writerのりんごさんらしさが出た印象に残る問題でした。

コンテストへのリンク
コンテスト後のツイート

B - Bishop

 H=1やW=1がコーナーケースの問題。TopCoderっぽい問題な気がします。

C - Sqrt Inequality

 誤差に気を付けましょう、という教育的な問題。幾何の問題だと、誤差に気を付けるのが重要かつ本質になることが結構あります。そういうとき対応できるように、普段から気を付けておかねば。

D - String Equivalence

 DFSで列挙する。最近のABCで何度か出題されていますね。
 私は、「再帰を使って列挙する」のは、結構難しいと思っています。

 DPで遷移を考えるのと本質的には同じかもしれませんが、大抵のDPでは、部分を切り取って考えれば良いのに対して、再帰では全体の構造を頭に入れておかないと混乱しやすい、というような。
 (あくまで印象です。本質的にはこの二つは同じはず)

E - Three Substrings

 貪欲したくなる気持ちを抑えて全探索する問題。

 コンテスト中も、全探索をしなくちゃいけないはず、とは気付いたのですが、実装に困って解けませんでした。
公式解説に書いてある方法をやるだけなのですが、意外と実装に苦労する問題ではないかと。

F - Fractal Shortest Path

 一応コンテスト後に自力でACしたけど、かなり時間がかかって大変だった……。
 
 「避けなければならないブロックは高々一つ」ということに気付けたら、後は二点の間にブロックがあるかどうかを大きい方から調べていく、という感じで解けるけど、この方針が立っても、実際に実装するのはかなり大変だと思う。

 AGCっぽい問題に見えるので、なんでABCで出題したんだろう? というのはちょっと不思議。確かに、発想すべき点は「避けなければならないブロックは高々一つ」くらいなので、発想より実装よりの問題、ということなのかな……。

FII Code 2020 Round #3

 Bまでの二完でした。A, Bの後Dを考えていて、実装終わらずに終了。とはいえ、仕方ない面もある。

コンテストへのリンク
コンテスト後のツイート

(このブログの記事は、主に、当時の自分のツイートを参考にして書いています。なら、そのツイートも載せておいた方が良いのでは? と思ったので、今後はできるだけ載せます)

Confused Robot

 これを飛ばしてDにいった。
 Sをひとまとめに見て、「それぞれの地点から、S一回分進むとどこへいくか?」を考えてダブリングするのが自然。
 が、これだとPython/PyPyで間に合うのか分からず(制限時間が長めなのも不安材料)飛ばした……んじゃないかと思う。あまり記憶にないけど。
 二個ほど他の人の提出を見たけど、大体そういう方針で解いているようだった。

 公式解説には、ダブリングを使わず、「S一回進んでも動かなくなる回数」を前計算すれば良いと書いてある気がする。けど、これって最大N*M回かかるのでは? よく分かりません……。

 O(N*M*S)でいけるなら実装してみようと思ったけど、よく分からないので実装してません。
 ダブリング解でもPyPyでちゃんと書けばACできそう……?

追記)ダブリングなしで「S一回進んでも動かなくなる回数」は求められることに気付きました。グリッドの各マスについて、「S一回でどのマスにいくか」を調べておけば、

・S一回後に自分自身に戻るマスは0回(以後Sを実行しても動かなくなる)
・S一回後に0へ行くマスが1回

 という風にして、DFS/BFSで求められますね。

 ただ、それを求めても、「S一回進んでも動かなくなるギリギリの回数」は最大N*M回あると思うので、O(N*M*S)では無理な気が……。(やっぱりダブリングが必要では?)

Double Palindromes

 コンテスト中ずっと考えていて、コンテスト後実装が終了したので提出したらTLEでした。
 Manarcherの解説はすぬけさんの記事が分かりやすい。
 その上で、アルメリアさんが解説している方法が使えます(この解説の例題の中に、この問題があります)。いや、この記事は勉強になりますね……。こういう観点で問題を見たことがありませんでした。

 これを読むと、コンテスト中に考えていたセグ木を使う方法は本質的には間違っていなかったようですが、BITを使うことで高速化できそうです。

 というわけで、もしかするとアルメリアさんの方法を使えばTLEにならないのでは? と思い実装してみたのですが、いつの間にか、CS AcademyではPyPy/Python3が使えなくなってました!(え?)

 「Non zero exit code = 127」というエラーが返ってきてどうしようもありません。

 Python3だとTLEなのは仕方ないですし……。まあ、しょうがないですね。
 勉強になったので良かったと思っておきます。

2020年6月23日火曜日

Codeforces Round #627 (Div. 3)

 時間ギリギリでFまで解き終ったのだが、BをHackされてしまった……。

コンテストへのリンク

A. Yet Another Tetris Problem

 「縦に二個」のブロックしかおけないので、mod 2で全要素が揃っていることが条件。

B. Yet Another Palindrome Problem

 四文字以上の回文があれば、その部分列で三文字のものを得られるので、三文字の回文を見つければ良い。
 三文字の回文は、「A B A」という形をしている(A=Bでも良い)ので、隣同士でない位置に、同じ数字があるか判定すればOK。

 Hackされたのは、実装方法がおかしくて(隣同士に文字があったとき消去するような実装をしていた)、同じ文字が三文字続いたときにダメという判定を返していたためでした。

C. Frog Jumps

 連続する「L」の個数の最大値+1。DP まとめコンテストのせいで、Frogという単語をみるとDPかと身構えてしまうけど、この問題ではDPは不要でした。

D. Pair of Topics

 式変形すると、$a_i-b_i>a_j-b_j$となるので、$a_i-b_i$をソートしてbisectを使って求める。

E. Sleeping Schedule

 結構読解に苦労した覚えがある。
 DP[h]を、時刻hに寝たときの「良い睡眠回数」の最大値とし、これを更新していく。

F. Maximum White Subtree

 全方位木DP(rerooting)を使う問題。このときも、かなり時間がかかってしまった……。
 いまだに全方位木DPには慣れないし、ライブラリ化もできていないけど、どうしようかね。色々な記事をパラパラと読んではいるのだけど、今一つピンと来ていないんだよね。

2020年6月22日月曜日

Educational Codeforces Round 83 (Rated for Div. 2)

 Eが解けなかった上、CがHackされて悲惨な出来。

コンテストへのリンク

B. Bogosort

 大きい順(降順)にソートすればOK。

・まず極端な場合を考えてみる

 という意味でもソート状態を考えてみるのが良い。

 また、立式して考えるのも良い。
 $A_i-i=A_j=j$でi<jのとき、必ず$A_i<A_j$なので、そうならないようにする、という方針でも思いつけると思う。

C. Adding Powers

 $k\geq 2$のとき、$k^n$は$k^1, k^2, ... , k^{n-1}$の和より大きい、ということを利用する問題。
 これが成り立つので、kのベキ達の和である数を作るためには、できるだけ大きいベキを使わなくてはいけない。

D. Count the Arrays

・Combi(m, n-1) : m個の数から、互いに異なるn-1個を選ぶ。(一組の数字が一致しているので、互いに異なるのはn-1個)
・n-2 : 一致する数を選ぶ。増加列と減少列の間には最大の数が来るしかないので、それを除いたn-2個から選ぶ。
・$2^{n-3} : 最大値と、一致する数を除いた残りn-3個の数を増加列か、減少列かのいずれかに割り振る。

 これで題意の配列が定まるので、この積が答え。

E. Array Shrinking

 コンテスト中に区間DPだと思いつつも解けなかった。
 DP[i][j]で、区間[i, j]が一つの数字に縮約できたときの値、とすれば良い。

 この定義は今見ると自然に見えるけど、確かに、「縮約したときの最小の長さ」をDPの値に持ちたいと思ってしまうと、ハマってしまうか……。

 そのDPテーブルを利用して、長さの最小値も求められる。(あるいは、けんちょんさんの記事のように、同時に求めることもできる)
 また、かっつさんの記事にありますが、もっと計算量は速くなるらしいです。

F. Attack on Red Kingdom

 一応、自力AC。最近Grundy数を復習したのを覚えていたから解けただけで、コンテスト時に解くことは無理だったと思う。

 こういうタイプのゲームはGrundy数を考えるしかない。
 x, y, zが小さいので、それぞれについてGrundy数を前計算しておく。

 問題は、$a_i$が大きいことだけど……。
 まあ、Grundy数はどこからか周期的になっているはずで、大きくても2520(1~10の最大公約数)周期にはなっているだろう、と思い、$a_i\geq 5400$のとき、$a_i =a_i$ %2520+2520$としたらACした。

 公式解説を見たら、周期もちゃんと調べてますね。

G. Autocompletion

 アルメリアさんの解説記事けんちょんさんの解説記事があったので解説ACしました。アルメリアさんの記事はかなり詳しく書いてあり、ありがたかった。

 Trie木について理解していなかったこともあって、問題文の読解から苦労してしまったのですが、やること自体はそこまで難しくないですね。
 ただ、「ある点から子孫の点へワープするときのコストの減少幅」がどの子孫へ行くときも一定というのは、意外な気がしてしまいました。これが直感的に正しいと思えれば解くのは難しくなさそう。

 Trie木の勉強にもなったので良かったです。これを機に、Trie木を使う問題を一つくらい解くべきかなぁ……。




2020年6月15日月曜日

日立製作所 社会システム事業部 プログラミングコンテスト2020

 Cが解けなかった上、Aで1ペナ出してしまい、レートを大きく下げてしまった……。それでも黄色に留まれたのは失敗に優しいと言われるAtCoderのレートシステムのおかげ。

コンテストへのリンク

C - ThREE

 コンテスト中、「木が二部グラフである」ことを利用することはすぐに思いついた。両方の色がN/3より大きいときの考察は上手くできていた。

 だが、片方の色がN/3より小さいときの方法を思いつかなかった。余り1か余り2のものを少ない方へ割り振らなくてはいけない気がして、少ない方に3の倍数を割り振る発想が起きなかった。
 そのせいで、もっと細かい連結成分をみたりしなくてはいけないのでは? と迷走。

 割り振るべきものは、「3の倍数、余り1、余り2」の三種類、色は二種類ある、ということを落ち着いて見直すべきだった。

D - Manga Market

 解説AC。
・ソートしてDP
・(待ち時間が)指数的に増加することに気付くと計算量を減らせる

 の二つを組み合わせた問題。
 どちらもそれほど発想し難いものではないけど、落ち着かないと厳しそう。

 特に前者については、

・とりあえず立式

 が重要。

E - Odd Sum Rectangles

 解説AC。
 公式解説動画を見ても、どうすればこの構築を思いつくのかが分からなかった。

 けど、正当性を証明することはできるので、こういう構築方法があると頭に入れておくのが良いのかな。

F - Preserve Diameter

 解説AC。解説を読んでからACするまで一週間以上かかっている気がするんですが、さすがに非効率過ぎない……?

 公式解説動画を(何度か)見れば、どういう計算をすれば良いかは理解できると思う。この問題は、発想するのは難しいけれど、解説を理解するのはそれほどではない、という印象。
 あとは確かに木DPをするだけなのだけど、その実装が難しくて戸惑った。

 解説pdfkmjpさんの解説記事に書いてある通りなんだけど、それでも難しいと思う。


 自分の実装はこんな感じです。

・まず、木の中心を見つける。(cとする。今回は中心が一点のときを書く)
・cからDFSする。親や、cからの距離を求めておく。直径をDとする。
・葉から木DPを行う。

 DP[x][p][m]($0\leqq p\leqq 2,0\leqq m\leqq 2$)を、頂点x(の部分木)まで見て、cからの距離が+D/2の葉がp個、cからの距離が-D/2の葉がm個のものの場合の数、とする。
 ただし、p=2, m=2は+D/2の葉が2個以上、-D/2の葉が2個以上、の意味。


 なので、最初に葉を見るとき、cからの距離がD/2か、そうでないかで初期状態を変える。あとは、一つ親のノードへ移るとき、この3*3の行列の遷移がどうなるかを考えれば良い。
(遷移もかなり難しいけど、辺に「+1, 0, -1」を割り振るとどうなるか? と、子が二つ以上あるときどうなるか? をちゃんと考えれば分かった)

2020年6月6日土曜日

TopCoder Single Round Match 780

 Easyが通ってレートは微増。

Div1 Easy BeatTheStar

点数が1点もらえる試合~N点もらえる試合が一試合ずつあり、二人のうちどちらかにその得点が入る。G点の試合が勝負の分かれ目になる(つまり、その試合を勝てたら合計得点でも勝ち、負ければ合計得点でも負け)確率は? という問題。

 余事象を考えると、G点の試合以外で、片方の点数が合計何点以下ならそういう状況にならないか、が分かる。なので、片方の点数がそれ以下になる場合をDPで求め、$2^{N-1}$で割って、1から引く。
 
 Python内fastestだったようなので、自分のコードはここで見ることができます。

Div1 Medium Prominence

コンテスト中は問題内容を把握できていなかった。kmjpさんのブログ記事で問題内容を把握しました。

 内容を把握できれば解法を理解するのはそんなに難しくないですね。

 kmjpさんの解法では前半パートにsetを使っていますが、公式解説を読むと平衡二分木は必要ない模様。左右から山を見ていけばできるのですね。
 後半パートもセグメント木ではなくSparse Tableを使えばO(N)になるのかな。(Pythonでは実行時間が厳しそうだし、実装はサボります。とはいえ、一応O(N)なので、上手く書けば通ってもおかしくはないですか)

AtCoder Beginner Contest 158

 時間ギリギリで全完。

コンテストへのリンク


D - String Formation

一々文字列を反転させていたら時間がかかってしまうので、反転は最後にまとめて行い、文字列追加の際、反転の回数が偶数なら後ろに、奇数なら前に追加する。
 先頭への文字の追加は、Pythonならcollections.dequeを使う。

E - Divisible Substring

 各桁に注目して、たとえば1234という四桁の数字を

4+3*10+2*10^2+1*10^3

 に分けて考える、というのはよくあるテクニック。
 この要領で、1桁目~各桁までの値をPに関する剰余で分類し、それが一致している範囲を求める。

 ただし、P=2や5のときに気を付けなくてはいけないことに注意。これらは10と互いに素でないので、この方法では上手くいかないため、例外処理しなくてはいけない。

 コンテスト中はこれに気付かずWAを出した後、全探索のコードを書いて比較することで気付けた。
 結構気付きにくいようにも思うので、上手くリカバーできて良かった。

F - Removing Robots

 とりあえずソート。

 すると、「そのロボットを起動させたとき、どのロボットまで連動して起動するか」が分かればDPで答えを求められそう、と分かる。
 なので、それをセグメント木を用いて求めていく。


2020年6月4日木曜日

Codeforces Round #626 (Div. 1, based on Moscow Open Olympiad in Informatics)

 A, Cの二完でレートが上がった。Div. 1回でレートが上がるのは嬉しい。

コンテストへのリンク

A. Unusual Competitions

 カッコ列に関する問題。結局、")"と"("の個数が同じときは、")"のとき-1、"("のとき+1としたときの累積和が負になっている部分の長さを足せば良いのだけど、これが最小かはちょっと悩むところな気がする。

B. Present

 AtCoderのTwo Sequencesとほぼ同じ問題だった。

 この問題は、PyPyだと制限時間が厳しいため苦労して解いたことをよく覚えている……のだけど、コンテスト中は全く思い出せなかったし、復習時も解法を思い出せなかった(AtCoderの公式解説を読み返せば分かったが)。うーん……。

・xor→桁ごとに考える

 というのは良いとして、その後が難しい。
 「和を取ったとき、ビットが立っている個数」を各桁について求めれば良いのだが、繰り上がりがあるので、その桁だけ見ているのでは分からない。「その桁以下の桁」を見て実際に和を取らなくてはいけない。

 そうすると、結局O($n^2$)かかってしまいそうなのだが、実は、二分探索を使って求めることができる。

 たとえば、四桁目を考えたとき、0000~1111までの二つの和は、0000~11110の範囲に収まるので、四桁目が1になるのは、1000~1111か、11000~11111の二つの範囲になる。
 和がその二つの範囲に入るindexを二分探索で求めれば良い。なお、PyPyだとそれではTLEになる(と思う)が、二分探索ではなく尺取りを使えば間に合う。

C. Instant Noodles

 問題設定は複雑だけど、内容を正しく把握すると、ユークリッドの互除法が分かっていますか? という問題になる。

 なお、コンテスト中は、Counter[tuple()]という書き方をしたらTLEしたので、Counter[hash(tuple())]という風に書いてACしたのだけど、普通に、dict[tuple()]という書き方でACできた模様。
 中に入れるものによっては、Counter()とdict()で時間に差がつくのですね。気を付けよう。

D. Reality Show

 公式解説を読んでAC。
 検索しても日本語での解説が出てこなかったので、さらっと解説を書きます。

(問題概要。制約は書いていませんが、二乗が通りそうな制約です)

 n人の人が順に並んでおり、それぞれにA[i](aggressiveness)と、C[i](cost)という整数が与えられている。index順にその人々を選ぶか、選ばないかを選択する。ただし、選ぶ場合は、A[i]の値が、今まで選んだ人の最小値より小さくなくてはならない。

 各人を選んだ時に、コストと利益が発生する。コストはC[i]、利益はaggressivenessの値によって決まり、aの人を選んだ時、P[a]である。(Pは負の値も取る)

 ただし、aggressiveness aの人が二人以上いた場合、そのうちの二人は戦い、残った一人のaggressivenessがa+1になる。この際、さらに利益がP[a+1]加算される。

 利益 - コストを最大にするような選び方をしたときの利益 - コストの値を求める。

(解説と雑感)

 公式解説でもある通り、まず、

・同じaggressivenessの人達を選んだなら、どのような順番に選んだとしても利益は同じになる

 ことに気付くのがポイント。

 設定が複雑なため分かりにくいけど、これは当たり前。
 人が二人で戦う、というイメージではなく、値aのスライム二匹合成されると、a+1のスライムになる、というイメージで捉えると納得しやすいと思う。
(今後、人ではなくスライムと書くことにします)

 このとき、indexを後ろから(aggressivenessの小さい方から)見ようと考えるのはまあ自然。そうすると、aggressiveness aのものを選んだら、今後a未満のものを考えなくてよくなる。

 ただ、こう考えても、今までのスライムたちがどう合成され、どんなaggressivenessのスライムが残っているか、を一々考えていては大変。

 だが、順番を変えても同じならば、一々合成させずとも、そのaggressivenessのスライムの個数さえ分かっていれば良い、と気付くのがもう一つのポイント。

 つまり、

・DP[k][cnt] = Aのmaxがkで, そういうスライムがcnt匹いるときの利益 - costの最大値

 としておいて、この値が更新されたときに、そのスライムを合成させたときを考えて、DP[k+1][cnt/2]などの値を更新していけば良い。(※)

 計算時間ですが、今見ているスライムのaggressivenessがaのとき、a+1の部分の更新ヶ所は1/2、次の箇所は1/4……なので、(更新順番を工夫すれば?)大体二乗になるようです。

 ただ、(※)を毎回行った自分の提出でもACできてしまいました。三乗でTLEになるだろうと思って投げたのですが。
 更新箇所がそれほど多くないので、三乗(のはず)の書き方をしても通ることが多いのかも?

 Div. 1のDということで身構えてしまうけど、解説を読むと意外と素直な問題でした。とはいえ、自力ACできるか、と言われると厳しそう。そもそも、コンテスト中にDを読みにいこうとはなかなか思えないですが……。