ラベル 最小値・最大値系は二分探索 の投稿を表示しています。 すべての投稿を表示
ラベル 最小値・最大値系は二分探索 の投稿を表示しています。 すべての投稿を表示

2020年2月9日日曜日

yukicoder contest 234

 四問目に苦労して四完で終了。コンテスト中に六問目を見ていれば解けた気がするけど、四問目で力尽きてしまった。

コンテストへのリンク

No.969 じゃんけん

 Xが0, 4, 10のときに、あいこの可能性がある。

No.970 数列変換マシン

・とりあえず立式してみる

 のが重要か。
 立式したら、とりあえず、$b_1+b_2+...+b_n$を考えてみる。それを使って$a_1+a_2+...+a_n$を表せる。

No.971 いたずらっ子

 南か東にしか行けない、というのを読み落とさないのが大切。

 また、あるマスのいたずらっ子には一度しか妨害されないので、再度そのマスに行くためには、前と同じルートを通れば妨害されずにそこまで行ける。

 なので、マス(i, j)までの最短時間は(i-1, j)または(i, j-1)までの最短時間から計算できる。

No.972 選び方のスコア

・とりあえずソート

 した上で、

・中央値→二分探索

 を考えるのは自然。問題は、どういう判定問題にすべきか。

 ソートされた数列、$a_1, a_2, ... ., a_n$の中央値が$a_i$で、残りが$2*k$個のとき、それらは一番大きい方からk個と、$a_{i-1}$から大きい順にk個とれば良いと分かる。

 ここで、kを一つ増やすことを考えると、取るべき数値は左右どちらもk個目に取った値より小さい。つまり、求めたいスコアへの寄与は単調減少だと分かるので、二分探索が使える。

 なお、取る個数が偶数個なときが最善じゃないことは、(公式解説の通り)一つ小さい個数へ帰着させることで分かる。

 中央値の位置で全探索することを思いつけば、取る個数で二分探索する発想は浮かぶと思うけど、何で探索すべきか思いつくのは結構難しい気がする。
 私は最初、左右からの累積和とかを考えてたけど、もっと落ち着いて、どういう風な値を選ぶのが最善か考えるべきだった。

No.973 余興

 こういうゲーム系は、

・真似っこなどの最適戦略

 がなければ、

・ゲームDP

 を考えるのが良さそう。(Grundy数を考えると分かりやすいこともあるか)

 今回は、制約を見ると区間i~jが残ったときに勝てるか? をDP[i][j]として区間DPができそう。ただ、更新にO(N)かかってしまい全体でO($N^3$)になりそうで困りコンテスト中は解けなかった。

 その後、公式解説や、けんちょんさんの記事、kmjpさんの記事では累積和を使えば良いと書いてあって、その方針を考えたんだけど、それでも私には分からなかった。

 結局、次のようにして解いた。

 DPを区間の小さい方から更新していく。その際、

・i~jの区間を使ったとき負け(DP[i][j]=0)だったら、そこへ遷移できる区間i~kやk~jでは勝ち

 なことを利用して、DP[i][j]=0となる場所が現れたときに、勝ちとなる区間を更新した。

 あるiを固定したとき、DP[i][k]=1を引き起こすDP[i][j]=0は一ヶ所だけなので、DP[i][j]=1を更新する更新回数は、左右からの高々二回。なので、この更新回数はO($N^2$)で収まる。

 だから、累積和など使わなくてもO($N^2$)で収まったと思う。

 最後の更新部分に累積和が使えると思うんだけど、正直よく分かっていない。この解法の方が自然じゃないかなぁ。

No.974 最後の日までに

 一応、今やったら自力ACできた。
 現時点での(お金, 好感度)を持ってDPし、「お金も好感度も低い状態」を枝狩りしたらACできた。

 解説の半分全列挙はなるほど、という感じ。
 でも、いくつか提出を見た感じ、枝狩りで通してしまっている人が結構いそうだった。

 ただ多分、この枝狩りでは本質的な計算量は減ってない気がする。Hack caseが作れる気がするんだけど、どうなんだろう。

 追記:test caseが追加され、MLEになっていました。半分全列挙しないと通らなくなったのかな。

2020年2月6日木曜日

Educational Codeforces Round 80 (Rated for Div. 2)

 時間ギリギリでDまでの四完。なんか遠回りばかりしてしまった。順位はあまり良くなかったけど、妙な解法でもACできたことで満足感はあった。

コンテストへのリンク


A. Deadline

 繰り上げを無視して式を整理すると、

・$x+\frac{d}{x+1}\leqq n$

 のようになるので、コンテスト中は、左辺を微分してxの最大値を求め、その付近を探索しました。

 実際は、移項すれば二次式になるので平方完成でOK。この時点でちょっとおかしかった。

B. Yet Another Meme Problem

 実験(もしくは式変形)してみると、B=99…99 という形のときに条件が成り立つことが分かる。

C. Two Arrays

 まず、"non-descending order", "non-ascending order"の意味が分からずタイムロス。いや、日本語では「広義単調増加」って言うから分からなかった気がしたけど、「単調非減少」も普通に使いますよね。これくらいの英語は読み取れないと……。

 その上で、コンテスト中は、A, Bの要素を左から決めていき、

・DP[i][j]=(Aの最後の値(つまり最大値)がi, Bの最後の値(つまり最小値)がjのときの場合の数)

 として、このDPをm回更新していきました。
 更新の際、新たなDP[i][j]は、前のDP[k][l]のk<=i, l>=jを満たす全ての要素を足したものになる(絵を描くと分かります)ので、累積和を使って更新することができます。

 ……と、これは制約を見ると自然な解法に思えますが、もっと簡単に解けますね。
 A+reversed(B)を考えると、これが単調非減少であればOK。その場合の数は、重複組み合わせを用いて、Combi(n-1+m*2,m*2)と簡単に表せます。 

D. Minimax Problem

  じゅぴろさんの解説動画が分かりやすい。

・最小値・最大値(や平均値・中央値)を考える問題は二分探索を使うと良いことが多い。

→今回は、判定の際にmが小さいことを利用できる。「答えがX以上にできるか」という判定問題を考えたとき、各行の数字はX以上か、X未満か、の二通りなので、判定に利用するbit列は$2^8$通りしかなくなる。それを全部試しても$2^{16}$通り。

 ……なんだけど、コンテスト中は、後半の判定を別のやり方でしようとしていた。$2^8$通りの二乗回探索すると間に合わない気がして、各bit列に対して「それと or をとったときに全てのbitが1になるようなbit列」を列挙しておこう、と思った。

 これを前計算しておく方針なら悪くなかった思うんだけど、実際は、各列に対して素数を割り振り、「bitが立っている列に対応する素数の積」によりコード化。その値に対して約数を列挙し、(列に対応する全ての素数の積)/(約数の一つ)となる値が存在するかどうかで判定した。

 無駄に面倒なことをしていて(計算時間も遅くなっているはず)、よくこれでACできたなぁ、と今思うと感心する。

E. Messenger Simulator

 今やったら自力ACできました。けんちょんさんのブログの解法(BITを使っている方)と同じ方法でした。

 シミュレーションを行うのは時間的に厳しいので、最小値、最大値がどこで変化するかを考えると、自分が選ばれていないときの位置変化は「±0か+1」なので、単調非減少。
 それをふまえると、各数字は変な位置変化はしないので、

・最初
・最後
・自分が選ばれたときの前後

 だけを考えれば良いと分かった。

 あとは、自分が選ばれたとき何番目の位置にいるかが分かればOK。これは、平衡二分探索木/C++のsetを使えばできそうな気もしたけど、私は持ってない。じゃあ、

・平衡二分探索木はしばしばBITで代用できる

 ので、できないか、と考えた。結局、

・各数字の位置を管理するリスト

を作り、

・BITで各位置までの出現個数を管理

して、選ばれた数字を一番最初(先頭の数字のさらに一つ手前)へ移動するよう更新していけば、BITにより選ばれた数字が何番目の位置かを調べることができた。