ラベル 二乗の木 の投稿を表示しています。 すべての投稿を表示
ラベル 二乗の木 の投稿を表示しています。 すべての投稿を表示

2026年5月8日金曜日

Next DP Contest

 F以外の4点以下の問題は正解。後は部分点を拾った。もう少し取れないとまずい。

コンテスト後のツイート

F - 集合 

 解説AC。テーマ一覧とArcAkiさんの記事を参考にしてAC。

 そもそも考察が難しい。
 最小値を使うか使わないかで考えると、その左側のみ、その右側のみ、両方を使う、で場合分けされるが、両方使う場合は最小値も必ず使わなくてはいけない。このことから、再帰を使って書けそうだと分かる。
 ……という考察部分に全く気付けなかった。

 この考察通り、再帰で書こうとすると、Cartesian treeの順にやることになり、そこでは二乗の木DPを行うことになる、という流れ。

 テーマを見てしまっていたので、この考察が分かった後はすんなり書けたけれど、実際はその後の部分も簡単ではない気がする。

J - 個数と総和

 この問題と同じテーマというのを見てAC。
 「繰り上がりを持つ桁 DP」と呼ばれているのは知らなかった。

 類題をACしたときは理解していたのだろうけど、DPテーブルを使い回して解くという解法を忘れていた。結構汎用性がある解法のようなので、ちゃんと身に着けておきたい。


N - ナップサック

 この解説記事を見てAC。
 証明はよく分かってないが、「DP[j]を求めるのに、最大重量Bに対して、3*B*B/j個効率良いものを見れば良い」ということは押さえておく。




2022年1月19日水曜日

HHKB プログラミングコンテスト 2022(AtCoder Beginner Contest 235)

 Eまで五完。

コンテスト後のツイート

F - Variety of Digits

 桁DP。
 コンテスト中は下の桁からやろうとしたが、上の桁からの桁DP(そこまで一致しているかのflagを持つやつ)を書いたらいけた。
 下の桁からでもできるかもしれないけど、そうやろうとして混乱するくらいなら上の桁からやった方が良いかね。


G - Gardens

 解説AC。
 包除原理+二項係数の和の高速化。
 どちらも無理な発想という感じはしないので、ちゃんと時間をかけたら解けたかもしれない。ただ、コンテスト中は包除原理を全然考えなかったので、厳しかったかも。

Ex - Painting Weighted Graph

 解説放送を聞いてAC。
 最小全域木+二乗の木DP(の亜種)。

 これも、まあまあ自然な発想を重ねれば解ける問題という気はした。最初の問題の見た目からは、二乗の木に行きつくとは想像し辛いので難しい。
 ただ、DPの遷移式を立てるところは難しい。これで良いはず……と思ってからWAをたくさん出してしまった。

2021年12月8日水曜日

AtCoder Regular Contest 130

 Bまで二完で終了。Bまで解いた後Fに行った判断が正解だったかどうか。

コンテスト後のツイート

C - Digit Sum Minimization

 公式解説は見ずにACしましたが、「繰り上がりの回数を増やせば良い」という本質情報を誰かのツイートで見た後でAC。
 「繰り上がりの回数を増やせば良い」ということが分かれば、まあ解ける。Pythonだと多倍長があるので、実際に数字を足して確かめることができるので楽ですね。

 コンテスト中は、桁DPかな、と思ったところで飛ばしてしまいました。
 「繰り上がりの回数を増やせば良い」は実験したら気付けた気がするので、この問題に集中していたら解けていた気もするけど、本当に実験コードを書こうと思ったかは分からないからなぁ……。
 

D - Zigzag Tree

 解説、解説放送を参照しつつAC。

 制約から二乗の木DPは考えたくなる。
 二乗の木DPは、「あるノードがルートだったとき、その部分木についての(何らかの)数」を持つDP。そのことが頭にあれば、「自分より小さい数が何個あるか?」を持ってDPすることが思いつける。これで(実際に実装しようとすると頭がこんがらがるけど、ちゃんとやれば)木DPは書ける。

 が、これを普通に書くと二乗にならず、累積和による計算量削減が必要になる。これは、式を見て冷静に考えれば分かるのだが、私はかなり悩んでしまった。

 木DPパートも計算量削減パートも典型といえば典型で、分かってしまえば難しいものではないし、解かなきゃいけない問題だとは思うけれど、実際にACまでもっていくのは結構困難に感じた。

F - Replace by Average

 コンテスト中に、a, $x_1$, $x_2$, ..., b(各$x_i$はaやbより大きい)という列に操作を行った最終結果がどうなるかは分かっていた。
 aの方が小さいとし、この列がn+1項とすると、(b-a)/nずつ等差数列のように大きくなっていく。余りはどうなるか? というと、1ずつbに近い方へと分配される。

 たとえば、
・3, 1000, 1000, 1000, 1000, 20
 に操作を行うと、
・3, 6, 9, 12, 16, 20
 のように、途中までは差が3、それ以降は差が4になって20へたどりつく。

 これは実験すれば分かった。
 公式解説ではこの証明に分量を割いていて、なかなか難しいが、問題を解くだけならこの結果を導くことができればOKだろう。

 こうしてできた結果が凸になることにはコンテスト中に気付いた。が、それを生かすことができず、値の小さい区間に対してこの操作を行う……みたいな方針へ走ってしまった。

 そこで凸包を作ろう、と思えたら良かった。
 こういうときアルゴリズムの勉強が大事ですね。凸包(convex hull trickやslope trickでも可)の具体的なアルゴリズムやイメージがちゃんと浮かべば、その方針を取れたと思う。

 凸包で得られた点たちに対して、その隣同士の点について上記の操作を行うと、かなり答えに近付く。実際、それを20回繰り返すことでACできた。(嘘解法だと思うけど、この回数がlogで抑えられそうな気もするので、もしかすると嘘解法じゃないかも?)

 正しい解法は、差が変わる点を適宜加えた(上の例だと、3と20だけでなく12も加える)凸包を作ればOK。
 1項目が3、6項目が20ならば商と余りを計算することで、「4項目が12になり、それが今作りたい凸包の候補になる」と分かる。この操作をしながら凸包を作っていけば良い。

2021年6月29日火曜日

AtCoder Beginner Contest 207

 ABCEの四完でした。D難しい。


D - Congruence Points

 すぬけさんの解説動画を見てAC。Pythonで$O(N^3)$でも65msでした。

 いやでも確かに難しいのだけど、Sの一点目から二点目へのベクトルが、Tのどの二点のなすベクトルと対応しているか? で二乗をかけるという方針は思いつかないといけないですね。
 そこまでできていれば、回転を複素数で計算という部分はできたかもしれない。本番中にも複素数を使うことは頭をよぎったはずなので。

 コンテスト中は、(他の人も結構ハマってましたが)角度を決め打って云々、という嘘解法にハマってました。

F - Tree Patrolling

 すぬけさんの解説動画を見てAC。
 二乗の木DPの問題は最近も解いていたのだから、自力でできなくちゃいけなかった。

 が、解説を聞いた後なのに、遷移を書くのに苦労したり、modを取り忘れたり、コーナーケースに引っ掛かったりとWAを量産してしまった。

 二乗の木DPで、子同士をマージするときの遷移は大体いつも似た感じいなるのね。FFTを使える形になるらしい。以前の二乗の木DPの問題でも、「遷移を書くのが面倒だったからFFTを使った」とか書いているツイートを見かけたけど、なぜそういうことができるか理解できた。

 それに気付けたのは収穫。

2021年6月7日月曜日

NOMURA プログラミングコンテスト 2021(AtCoder Regular Contest 121)

  Cまで三完。Cで苦戦してしまったが、レートはそこまで落とさずに済んだ。


C - Odd Even Sort

 三文字以下なら、交互に操作し続けることでソートになる、というのは気付かなければいけないけれども、後は大きい数字を右に移動させていくだけで良い。そうすると、自然に規定回数以下に収まる。
 後は実装問題なので何を反省すべきかね……。
 とりあえず、WAやTLEがでたときにすぐにcheckerを書いて(結局、何回かペナを出した後で書いた)いれば、ペナルティ量産は防げたと思う。

D - 1 or 2 

 解説AC。

 ツイッターなどでヒントを見ていたのでどこまで自力かは分からないけど、常に二つ選ぶなら、ソートして大きい方と小さい方から取っていくのが最適、というのは気付いた。
 あとは、一個だけで取るものをどう選ぶかだけど、ソートしたものの中である区間になっているはず。その区間全探索を普通にやると三乗だけど、何か差分計算とかで高速化するのかなぁ……とか考えながら解説を見たら、思った以上にシンプルでした。

 なお、解法ツイートを見ると、(高速化は分からないけれど)正負などで場合分けして一個取る場所の範囲を絞れば通るみたいですね。

E - Directed Tree

 解説AC。
 ……といっても、公式解説を読んだだけではなかなか理解できず苦労してしまった。

 木の問題で、木DPをするのでは?(制約を見ると、二乗の木DPかも?) 使ってはいけない数字が指定されるので、包除原理を使うかも? といったあたりは考えた。が、そこで詰まってしまった。
 キーワードはこの問題と共通ですね。そして、今回の問題はこの問題よりDPを立式するのは簡単なはず。とはいえ、なかなか立式するのは難しい気がする。

 結論からいうと、解説の通り、

・$DP[i][j]$を$i$を根とする部分木に$j$箇所条件に違反するように書き込む方法の個数

 とするのだけれど、この「部分木」というのは、その部分木の祖先がどうなっているのか、とかとは全く関係ない、本当にただの部分木です。その部分木に関する入力が与えられたら、その答えを求めるものです。

 ……いや、素直に解説を読めばそう(だし、二乗の木DPを使う問題ではそういう風に置くものなのかも)なんですが、私は、祖先のノードが何個あるから禁止すべき個数は……などと考えてしまいました。
 それだと遷移が上手くいかなくて、ただの部分木に関する問題のDPテーブルが求まっていたら遷移が上手くいく、というのは不思議です。

 あと、この問題の難しさは、包除原理を使うなら、$DP[i][j]$の$j$は必要なさそうなのに、$j$を明示的にしなくてはいけない、という部分だと思います。最終的に、$j%2$によって足すが引くかを決めるので、$DP[i][2]$で良いのでは、と思ってしまいそう。

 ただ、$DP[i][j]$の$j$があっても二乗に収まる、というのが二乗の木DPなので、二乗の木DPを使おうという気持ちで臨むとDPの立式もしやすいのかもしれない。


 どうDPテーブルを置くかが難しい問題な気がしたけど、二乗の木DPに慣れていれば分かるのかも? とも思えてきました。
 練習すれば、DPの置き方も含めて典型と思えるようになるのかな……。

(FはACした後書くつもり。)