2021年1月30日土曜日

AtCoder Beginner Contest 190

  全完したものの、Eに時間かかりすぎ。


C - Bowls and Dishes

 最初、この前のARCのBみたいにUnion-findを使う問題に見えた。
 が、ちょっと考えるとそれでは上手くいかず、Kの制約を見てbit DPに気付いた。

D - Staircase Sequences

 とりあえず立式!
 lからrまでの数の和の公式を使って立式すると、条件が分かる。

E - Magical Ornament

 Kの制約からbit DPっぽい……とは思ったけれど、最初それをどう使えば良いか分からなかった。
 $C_i$達同士の距離を求め、どう進むか順番(Kの順列)を決めたら答えは求まるけれど、それだとK!の計算量がかかってしまう。

 そうやってしばらく考えた後、この問題は巡回セールスマン問題に似ていると気付いた。それでもどう解くか分からなかったけれど、巡回セールスマン問題がbit DPで解けるということは聞いたことがあったので検索。このページを見て、なるほど、と思い実装した。

 最初気付かなかったのは仕方ないとしても、巡回セールスマン問題をbit DPで解く方法は頭に入れておかないと。

F - Shift and Inversions

 とりあえず転倒数は求められる。
 あとは、最初の数字を抜いたとき&それを最後に入れたときについて差分計算すれば良い。


Educational Codeforces Round 103 (Rated for Div. 2)

 pretestはDまで。
 Eはコンテスト五分前に正しい解法にたどりついた。


B. Inflation

・他の要素に足すより、P[0]に足した方が得だと気付く
・二分探索

 でOKだけど、第一点目は結構思いつきにくい気も。
 なお、二分探索の上限の見積もりを間違えて1WA(k=100のときが一番大きくなるかと思った。本当はk=1のとき)。安全に上限を定めておくべきでした。


E. Pattern Matching

 さっきのyukicoderでもトポロジカルソート関係の問題が出てたんだから、これは解けなくちゃいけなかった。

 難読(だし、日本語で簡潔に説明するのも難しい気がする)だけれど、読み解ければ、各string sに対して、それとパターンマッチするものたちを列挙したとき、その中でmt(並び替える前のPの中でmt番目のもの)が最初になるように並び替えれば良い、という問題になる。

 パターンマッチするものを列挙するところでO(n*m)かかる気がしたのだけど、k<=4なので2^4でいける。
 k<=4にちゃんと着目すべきだった。

yukicoder contest 280 (門松コンテスト)

 Dまで五完でした。Eは解説と同じことを考えていたのに、なぜかそれではまずいと思い込んでしまった。


No.1369 交換門松列・竹

 二点の交換しかできないので、たくさん門松列になっていないものはそもそもダメ。少なくとも一つはダメなところを交換に使わなくてはいけないから、「ダメなところとそれ以外のどこか」を交換する全探索すれば良い。交換後に門松列列になっているかどうかは、元々門松列になっていて交換に絡まないところはチェックしなくて良いので、「ダメだったところ」と「交換した箇所の周囲」だけチェックすればOK。

 この「たくさん」とか「周囲」というのが具体的にどれだけか考えるのがややこしくてWAを出したけれど、安全を取って大き目に取ればACできました。
 最初から大き目に取るべきでした。

No.1370 置換門松列

 公式解説と同じことを考えていたけれど、同じことをやっても(Phase 1のチェックをしていても)A[i]=A[i+2]となるケースを除けない気がしてしまった。

 トポロジカルソート順に決めたとき、別の数に同じ数字が割り当てられる可能性がある気がしてしまったせいだと思うんだけど、そんなことは起こり得ない。
 検証したら気付いたと思うので、変に悩まず、図を描いたり実装したりすべきでした。

2021年1月26日火曜日

Codeforces Round #697 (Div. 3)

 pretestは全完→システムテストも全完でした!
 BCがちょっと難しい気がしたけど、後半は簡単めに感じました。


G. Strange Beauty

 特にツイートに付け足すことはないのだけど。
 エラトステネスの篩風に約数列挙するのは忘れないように!

2021年1月24日日曜日

TopCoder Single Round Match 798

 Easyをパッと思いつけなかった上、Pythonじゃ通らない制約のためC++へ書き直したためタイムロス。そのためMedが解きおわらず。


Div1 Easy SuperSubset


 部分和問題なのでDPしようとは思えて、あとは上手く辻褄合わせれば良い。
 具体的には、初期値をDP[0]=pow(2,len(A))にして、一つ使ったら自由度が半分になるので、
・DP[i+a]+=DP[i]/2
 みたいに遷移させれば良い。

Div1 Med ExpectedValue


 コーディングフェーズ終了三分後に解き終わった。
 A[i]=jのとき、iとjを結ぶとすると、N個の要素が何個のグループに分けられるかで答えは決まる。
 愚直解を書いてグループの個数を調べると、
・DP[i][j]=(DP[i-1][j]*(i-1)+DP[i-2][j-1]*(i-1)
 のように推測でき、これで答えを求めたら通った。

 以下、Practiceでシステムテストを通ったコード。

mod=10**9+7
DP=[[0],[0],[0,1],[0,2],[0,6,3]]

for i in range(5,1501):
    X=[0]*(i//2+1)
    for j in range(1,i//2+1):
        if i%2==0 and j==i//2:
            X[j]=(DP[-2][j-1]*(i-1))%mod
        else:
            X[j]=(DP[-1][j]*(i-1)+DP[-2][j-1]*(i-1))%mod
            
    DP.append(X)
        

class ExpectedValue():
    def solve(self, N):
        S=sum(DP[N])
        ANS=0
        for i in range(len(DP[N])):
            ANS=ANS+i*DP[N][i]
            ANS%=mod
        return (S*N-ANS)*pow(S,mod-2,mod)%mod

AtCoder Beginner Contest 189

 Eまで五完でした。


C - Mandarin Orange

 まず、最大長方形だと気付けないとダメ。
 その後は、ここを見ながら実装したけど、これは覚えておくべきなんですよね。スタックを使うことは覚えていたけど、その後どうするかがパッと出てこなかった。
 多分、時間があったら自力でできただろうけど、ABCのC問題で時間をかけちゃいけないしね……。
 とはいえ、調べてでもできたのは良かった。調べてもできないよりは、ずっと良い。

E - Rotate and Flip

 最初行列を考えたのに、行列での解法が分からずちょっと遠回りな実装をしてしまった。回転の行列は分かるけど、他はどうやるんだろう、と。

・二次元のアフィン変換は3*3の行列で表せる

 は頭に入れておいた方が良さそう。

F - Sugoroku2

 期待値を求める問題はいつも立式の段階で戸惑ってしまう。
 今回も、この問題の解説記事を読まないと正しく立式できなかった。
 立式した後は正しい方針を取れていたので、あとは慣れかなぁ……。

2021年1月23日土曜日

yukicoder contest 279 (Zelkova and Cherry)

 BCEの三完でした。


No.1358 [Zelkova 2nd Tune *] 語るなら枚数を...

 解説AC。
 制約を見ると、Y/max(N, K, H)で全探索してくれと言ってそう。
 あとは、$ax+by=c$という形の方程式の解の個数を求めれば良い。……のだけど、そこでコンテスト中は詰まってしまった。

 が、落ち着いて見ると、これは大学受験数学の典型。

 拡張ユークリッドの互除法で$ax_0+by_0=c$となる$(x_0, y_0)$を一つ求めて、$a(x-x_0)+b(y-y_0)=0$とすれば、$a(x-x_0)=b(y_0-y)=abk$とおくことで、x, yが0以上という条件をkの条件に言い換えられる。

 不定方程式の問題だと思えばできたはずなんだけど、何かで探索したい、という気持ちでいると気付かなくなってしまう……。良くないね。

2021年1月20日水曜日

Codeforces Round #696 (Div. 2)

 Dまで四完でした。Bで詰まったけど、なんとかDまでいけたのは良かった。


E. What Is It?

 $i, j$を置換した結果、iは正しい位置へ来る。なので、swap回数はn-1回。
 この条件を満たして$(j-i)^2$が最大になるような置換順を一つ見つければ、最終配置(ソート済み)から遡っていくことで初期配置は求められる。

 で、各iについて、j=1かj=nのどちらかで$(j-i)^2$が最大になるので、iがn/2より小さいときはnをペアに、そうでないときは1とペアにすれば良い。

 問題文がちょっと分かりにくかったり、出力形式が分かりにくかったり($p_j=i$となる(i, j)を出力する。(j, i)と逆順に出力してはダメ!)するけれど、実質は簡単でした。


2021年1月18日月曜日

パ研合宿2020 第2日「パ研杯2020」

 マラソン(ヒューリスティック)形式の問題が含まれるコンテストでは、まずその問題から見るようにしています。
 その問題にちょっと不備があった、というのが後の出来に多少は影響したかもしれないけど、Cで苦戦しDが解けない、というのは情けなかった。


C - A + B

 次数を考えれば良い。
 思いつけばシンプルだけど思いつかないと厳しい……というタイプの問題なので仕方ない面はあるけど、次数を考えるのは定石の一つなので、素早く思いつきたかったところ。

D - Animal Show

 動物aにアレルギーを持っている人が全員ショーを行ったら、動物aを使ったショーができる。ということは、人を頂点とし、ある動物に対して「アレルギーを持つ人」から「ショーで使う人」に辺を引いて、トポロジカルソートをすれば良い。(ちょっと戸惑ったけど、ここまではコンテスト中に分かった)

 ただ、これだと辺の数が大きくなり過ぎる。
 なので、グラフを陽にもたず、「各動物について、その動物のアレルギーを持っている人は何人か」「各人について、まだショーに使用できない動物は何種類か」というリストを持っち、それぞれが0になったら適切な処理をして……として解いた。

 これ、基本的にはKahnのアルゴリズムをやろうとしています。入次数が0になるところを探す、という処理をしている。
 ただ、そういうとことをしようと思っても、色々混乱して正しいコードにするのにはかなり時間がかかってしまった。

 公式解説はシンプルですね。
 頂点を増やした方が辺が減るのか。なるほど確かに。

E - 老朽化対策

 解説AC。
 なるほどとは思うものの、そもそも座標の最大値・最小値をあまりチェックしていなかったので思いつけなかった気がする。確かに、$5*10^4$とちょっと小さめなのは気になったけど……。こういうところにも気を配らなきゃいけないのね。

 最近、平方分割系の問題に続けざまに出会った気がする。(これこれ

2021年1月16日土曜日

キーエンス プログラミング コンテスト 2021

 Dまで四完でした。一ヶ月くらいレートマイナスが続いていたので、プラスでほっとしました。


C - Robot on Grid

 文字が書かれていないマスでは、2/3の確率で右にいけ(RかXを書き込んだとき)、2/3の確率で下にいける(DとXを書き込んだとき)、と考えると上手くいった。

D - Choosing Up Sides

 このコンテストのEの類題。
 類題と気付くまでも気付いてからも時間がかかってしまったけど、復習したときもよく理解せずACしたので仕方なかったかな、とも。
 「アダマール行列」というキーワードがでてきたので、「高校数学の美しい物語」の該当ページくらいは理解しておきたい。

E - Greedy Ant

 すぬけさんの解説動画を見てAC。
 自分も含め、区間DPっぽいと思った人は多いはず。さらに、制約から三乗が可能そうなので、もう一つ何かパラメーターに持って、DP[l][r][k]のようにできる。ただ、このkを、ターン数のように考えたのでは上手くいかない。

 解説動画では「貯金」という言葉を使っていた。(ツイートを見ると、保留とか予定調和DPという言葉で説明しているものも)
 (問題文中の)すぬけ君はどの飴でも取れるのだけど、区間DPに落とし込みたいなら、今考えている区間の飴を取るようにする他なく、だとすると、他の箇所の飴を取る権利はDPの区間がその場所に来るまで貯めておく……というのは結構自然ですね。

 なんか用語があった方が記憶に残りそうなので、「予定調和DP」という言葉で覚えておくことにしたい。




(解説動画を見たところ、Fはちょっと厳しそうだったので今解くのはやめます。主にFFTへの理解が浅いことが原因です……。勉強しないと。)

Codeforces Round #695 (Div. 2)

 Cまで三完。
 Aからペナを出しやすい問題で、Bがかなり難しい、というDiv. 2としては珍しい回でした。


D. Sum of Paths

 解説AC。
 配列の各要素それぞれについて、何回ずつpathが通るのかを調べるのがこういう問題の定石。
 ただ、それをどう求めれば良いかが難しい。
 i番目の配列はi-1かi+1から来る……ということから、長さjのときの最終地点になるものが何個ずつあるかは求められるけど、それだけでは上手くいかない。

 公式解説と同じくDP[i][j]で、「長さj, i番目のcellで終わるようなgood pathの個数」とする。

 コンテスト後に思いついた方法は後ろから見るものだった。

 終着点がDP[・][k]と分かっているから、それぞれがどこから来たかを調べていく。DP[i][j]はDP[i-1][j]とDP[i+1][j]から来ていることを考えれば、その割合が分かる。

 たとえば、DP[i][j]のうち、i-1から来た分は、DP[i][j]*DP[i-1][j-1]/(DP[i-1][j-1]+D[i+1][j-1])と求められる。

 ……としていけば答えを求めることができる。(TLEの提出。PyPyだと厳しい気がするけど、高速な言語ならこの方法で通せるかも?)


 公式解説はもっと賢かった。
 j歩目でi番目のcellを通るものは、前から見るとj歩でこのcellにたどりつき、後ろから見るとk-j歩でこのcellにたどりついたものである。
 よって、DP[i][k]*DP[i][j-k]が求めたいものだ! というもの。
(DP[i][j]が、「i番目のcellから始まる長さjのgood pathの個数」とも捉えられることを使っている。)

 この方法だと割り算する必要がないため、PyPyでもTLEせずにACできる。

2021年1月15日金曜日

yukicoder contest 278

 Fまで六完。Eで苦戦してしまったけど、F解けて良かった。


No.1339 循環小数

 解説を読むとシンプルですね。
 検索で解法に至れた(よく分からないまま提出してしまったけど、自分のコードは解法と一致してそう)のは良かったけども……。

 オイラーのφ関数を頭に入れておかないと。

No.1340 おーじ君をさがせ

 行列累乗はすぐ思いついたので、時間もなかったし、適当なmodで投げたら通った。

 しかし、解説のように別の演算で行列累乗するという方法は浮かばなかった。解説にもある類題は解いたことあったのに、思い浮かばなかったのは良くない。

Educational Codeforces Round 102 (Rated for Div. 2)

 pretestはDまで四完→結局四完でした。


E. Minimum Path

 ダイクストラする以外なさそう。
 ……というのは正しかったが、そこから進まなかった。

 koboshiさんの解説jupiroさんの解説で理解。頂点や変を倍にしてダイクストラする(拡張ダイクストラ)のは典型なのに、全く思いつかなかった。

 なお、PyPyだとTLEしないで解くのは難しそうなため、ACしていません。

2021年1月12日火曜日

AtCoder Beginner Contest 188

 Fが解けずに終了。これは反省しないと。

F - +1-1x2

 AGCで類題が出題されていた。
 このときのAGCは一問も解けず終わってしまったため非常に悔しく、印象に残っていた。

 そのため、Fを見たとき、まずこの問題を思い出した。
 ……が、スタートの数Xが定まっていないと上手くいかない気がして捨ててしまった。

 実際は同じ方法で上手くいく。解法をきちんと理解していれば分かったはずなので、反省。



 その後、「2倍する回数を決め打つ」という方針を思い付く。が、1WAが取れずに終わった。
 これも方針が悪かったわけではなく、koboshiさんの解説や、fairy_lettuceさんの解説のように、正しく実装すれば通る方針だった。

 自分のコード内で、「$1, 2, \dots , 2^a$及び、$-1, -2, \dots , -2^a$を使って、xが作れるか」というのをpartition(a, x)と書き、メモ化再帰で調べようとしている。このとき、負の数も使えるというのが重要で、たとえば、「$1, 2, 4, 8$で$7$が作れるか?」だったら、「4+2+1」ではなく、「8-1」とできることに注意しなくてはいけない。

 このpartition(4, 7)という例だと、上の桁から見ていった場合、

・partition(3,7)
(1, 2, 4で7が作れるか?)
・1+partition(3, 7-8) = 1+partition(3, 1)
(1, 2, 4で-1を作れるか? 正負は共に使えるので、1, 2, 4で1を作れるか? と同じ。)

の二通りを考えれば良い。


 その辺り、一応分かっていたはずなのに、一番上の桁でのみ負のを考慮するようにしてしまっていた。上の桁から順に見ていったのだが、最初だけマイナスを考慮すれば良いと勘違いし、そこから抜け出せなかった。

 各桁の使い方はプラスとマイナスで二通りあるのだから、プラスとマイナスの両方を考慮するのは自然ですね。小手先の変更で通そうとせず、落ち着いて考えればこの方法でも通せたはず。
 これも反省。

2021年1月11日月曜日

AtCoder Regular Contest 111

 ABの二完で終了。
 復活してからのARCでは失敗が少なかったのに。悲しい。


A - Simple Math 2

 かなり長いこと分からなかったのですが、
$10^N=p*M+r$
 とおき、さらに、
$p=q*M+ANS$
 とおいて、下式を上式に代入すると考えると、$M^2$での余りを考えれば良いと気付けました。

B - Reversible Cards

 既出らしいですが、気付けませんでした。
 当時は解けていなかったので、今回は解けて良かった。

C - Too Heavy

 ツイートか何かで「重い荷物の順番に交換すれば良い」というほぼ答え同然の情報を得ていても、30分程度実装にかかってしまった。

 どういう問題なのか、情報を整理するのか難しいと思う。

 頭の整理のため、

・B:荷物の番号→荷物の重さ
・P:人物の番号→持っている荷物の番号
(及び、・Pの逆関数:荷物の番号→人物の番号)

 と、それぞれがどういう関数かメモしたら多少分かりやすくなったが、それでも苦戦した。

D - Orientation

 iとjを結ぶ辺で、C[i]とC[j]が異なるなら、Cが大きい方から小さい方へ結べばよい。
 問題は、同じときにどうするか、だが、公式解説にある通り、よく考えるとDFSするだけで良い! となる。

 確かに、よくDFSの性質を考えてみればそうなりそうで、証明はDFS木などを使えばできる。

 ただ、それが自然と感じられるためには、DFS木、lowlink、橋といったあたりに親しんでいることが必要そう。勉強不足でした。
 今調べた中では、hosさんのpdfが一番分かりやすかった。AOJにも入っている内容なので、ちゃんとやっておかねば。

E - Simple Math 3

 snukeさんの解説動画を見てAC。
 floor sumを使うと分かっても戸惑ってしまった。けど、その知識があるなら解けなきゃダメですね。図を描いて、順を追って考えればfloor sumが出てくるという問題なので。

F - Do you like query problems?

 snukeさんの解説動画を二回見てAC。
 一回解説動画を見た時点だとよく分からなかったのだけれど、もう一回見たら意外と単純だと分かりコードにできました。

 解説動画を理解する上でのポイントは、
・N=1のとき、「k回目のクエリでsumを取るときに加算される値」が$C^k*$(係数)の和
みたいに表せるので、kを1~Qで動かした和が等比数列の和の公式で求められ、大体O(1)なこと。(繰り返し二乗法を使うので本当はO(1)ではありませんが)
・N=1の場合で求めたA, X, Bが、Nが一般の場合でも求められること

 といった辺りだと思います。

 何を教訓とすべきかは難しいです。特に、一回解説動画を見た後もできなかったのは、何かが思いつかなかったのではなく、複雑な状況を理解し式に落とす力がなかった、という感じなので。
 こういう、複雑な状況を整理する力はどうすれば身に着くんでしょう。マラソンをやると効果があったりしないかな……。

2021年1月10日日曜日

TopCoder Single Round Match 797

0完ですがHack1成功でレートは若干プラス。

Div1 Easy FlightPlan

 使える高さを固定すれば、スタートからゴールまで最低何歩で行けるかはBFSで書けるので、高さを全探索すれば良い。ただ、$O(N^4)$をPythonで通すのは無理。頑張ってC++を書いたものの、システムテストで落ちてしまった。

 注意すべきは二点。

・(BFSの書き方にもよるけど)スタート地点が全探索時の高さより高いときバグる可能性がある。
・オーバーフロー。全探索する高さをintにしていたら、高さ*cupなどの計算でオーバーフローしていた。

 この二つに引っ掛かってたのでダメ。特に一点目は、Pythonで書いててもミスってた可能性がありますね……。



 以下、システムテストを通ったコード。

#include <bits/stdc++.h>
#include <bits/stdc++.h>
using namespace std;

class FlightPlan
{
public:
  long long fly(int R, int C, vector<int> H, int cup, int cdn, int clr)
  {
    set<long long> S;
    S.clear();
    vector<long long> DP(R * C);

    for (int i = 0; i < R * C; i += 1)
    {
      DP[i] = 9999;
      S.insert(H[i]);
    }
    DP[0] = 0;

    long long ANS = 1000000000000000000;

    for (auto s : S)
    {
      if (s < H[0])
      {
        continue;
      }
      if (s < H[R * C - 1])
      {
        continue;
      }
      deque<int> Q;
      Q.push_back(0);

      for (int i = 0; i < R * C; i += 1)
      {
        DP[i] = 9999;
      }
      DP[0] = 0;

      while (Q.size())
      {
        int x = Q.front();
        Q.pop_front();
        int r = x / C;
        int c = x % C;

        if (r + 1 < R && H[(r + 1) * C + c] <= s && DP[(r + 1) * C + c] > DP[r * C + c] + 1)
        {
          DP[(r + 1) * C + c] = DP[r * C + c] + 1;
          Q.push_back((r + 1) * C + c);
        }

        if (r - 1 >= 0 && H[(r - 1) * C + c] <= s && DP[(r - 1) * C + c] > DP[r * C + c] + 1)
        {
          DP[(r - 1) * C + c] = DP[r * C + c] + 1;
          Q.push_back((r - 1) * C + c);
        }

        if (c + 1 < C && H[r * C + c + 1] <= s && DP[r * C + c + 1] > DP[r * C + c] + 1)
        {
          DP[r * C + c + 1] = DP[r * C + c] + 1;
          Q.push_back(r * C + c + 1);
        }

        if (c - 1 >= 0 && H[r * C + c - 1] <= s && DP[r * C + c - 1] > DP[r * C + c] + 1)
        {
          DP[r * C + c - 1] = DP[r * C + c] + 1;
          Q.push_back(r * C + c - 1);
        }

        if (DP[(R - 1) * C + C - 1] < 9999)
        {
          long long ANSX = 0;
          ANSX += (s - H[0]) * cup;
          ANSX += (s - H[(R - 1) * C + C - 1]) * cdn;
          ANSX += DP[(R - 1) * C + C - 1] * clr;

          if (ANS > ANSX)
          {
            ANS = ANSX;
          }
        }
      }
    }
    return ANS;
  }
};

2021年1月8日金曜日

パ研合宿2020 第1日「SpeedRun」

 Dと最後三問が解けずの11完でした。


D - 立方体を壊せ!

 立体図形は苦手なので飛ばしたが、落ち着いてやれば解けることは解ける。

 まず、$x+y+z=K$という平面は、(K,0,0),(0,K,0),(0,0,K)を通ることをイメージする。立方体の頂点から各辺(座標)の方向に、切片が1ずつ動いていくイメージ。
 それができれば、$K<N$のときが三角錐で、$K>2*N$のときが、立方体から三角錐を除いたものだということが分かる。

 問題は、$N<K<2*N$のときだけど……。
 ちゃんと図を描けば、$K<N$のときと同じように作った三角錐から、角の三角錐三つを切り取ったものだと分かった。

 あまり立体が得意でない私でも分かったのだから、他の人でも分かるはず! と言うのは容易いけど、私より立体が苦手な人だっているはずで、そういう人にどう説明すれば良いんだろう。
 図を描くこと自体にそこそこ立体図形の能力が必要な気がするしねぇ。

 私自身、もう少し捻られたら自力で解くのが難しくなる気がする。(いや、色々断面描いて頑張るけど……)
 数学を勉強しても、あまりこういう立体をイメージする能力は伸びない気がする。厳しいね。

H - その計算、合ってますか?

 コンテスト中にWAを量産してしまった。
 各ビットごとに考えると、orが1のところでしかandやxorは1にならないことが分かる。

 あとは、「andが1のところは一斉に1を取る」ことに気付くのが重要。偶数回出現すればxorは0、奇数回ならxorは1になる。

 各ビットごとに考えていると解けない問題。
 とはいえ、各ビットごとに考えるのは基本なので、そう考えてハマってしまうのはある程度仕方ないか。

J - Output-only

「5, 12, 13」で検索したらこのページがでてきた。なるほど。
 見たときは何これ、と思ったけど、よく見れば自力でも思いつけそうな式か。

M - 貢ぎ物

 結構考えたけど分からず、解説を見てAC。
 「高速ゼータ変換」というキーワードを見たがどういうものか忘れていた。この問題のすぬけさんの解説を見たのは結構最近なのに……。名前と内容が結びつきにくいので、覚えにくいのかも。
 しかし、そのキーワードを見ても解けず。

 各$A_i$が$2^{20}$以下なので、$2^{20}$要素のDP配列を持つことは思いついた。だが、その配列を「その数を作れるか、否か」の01だけを持つものとしても上手くいかない。

 それを、DP[i]で「iに含まれる数j達で作ることができる最大の数」(つまり、DP[j]たちをorしたもの)とすると上手くいく。

 $2^{20}$要素の配列を持つというところはあってそうだけど、01だけを持つのは無駄なので、他に何か持てないかな……? と考えたら思いつけたのかな。

N - 背の順

 コンテスト後に問題を見て、自力でできるかと思ったら苦労してしまった。

 二つ以上の区間を消す必要はない。なぜなら、$[l_0, r_0], [l_1, r_1]$を消すなら、$[l_0, r_1]$を消した方が得だから。

 $[L, R]$を消す場合を考える。
 $L$の候補は、左端$A[0]$か、左から順に調べて$A[i-1]>A[i]$が成り立つ$A[i]$のどちらかになる。そのような$i$に対して、$j>i$なる$j$を消すと消した後がソート済にならないし、$A[1]~A[i-1]$を消すなら$A[0]$や$A[i]$を消した方が得なので。

 その後、$R$も同じように、候補が二個程度に絞れる……と思ってWAを出してしまった。
 左右対称な気がしてしまったが、実際はそうではない。

 $L=0$のときは簡単。
 $A[i], A[i+1], \dots $がソートされているような最小の$A[i]$が最適なRになる。

 もう片方の場合も、
 $R$は「$A[i], A[i+1], \dots $がソートされている」ような範囲の中にある。求める$R$は、この範囲で、
・$A[L-1]<A[R+1]$
 が成り立つような最小の$R$である。

 たとえば、

・$A=4, 1, 2, 3, 5$

 の場合を考えると、$L$の候補は$A[0]$と$A[1]$だが、$L=A[0]$に対応する$R$として最適なのは$A[1]$, $L=A[1]$に対応する$R$として最適なのは$A[3]$となる。


 公式解説がDPだったのは驚き。全く考えなかった……。

O - Xor Sum Sum

 解説AC。

 最上位bitから考える……といった方法も頭をよぎるけど、A,Bの数字が小さいので、なんらかの方法で全探索するのかな、とは考えた。

 が、基底を考える方針は全く浮かばなかった。言われてみればなるほど、という感じでした。
 xorの掃き出し法は、この問題この問題を解いたことがあったのにね。

 なお、xorの掃き出し法は、noshi91さんの方法が簡単。これで良いと納得するのには苦労したけど、一旦分かれば忘れない方法ですね。

2021年1月6日水曜日

Codeforces Round #694 (Div. 1)

 pretestはCまでの四完だったが、システムテストでCが落ちてしまった。


C. Strange Shuffle

 詐欺師(impostor)の周囲の値が変わっていくことに着目。右側はkより大きく、左側はkより小さくなる。「kでない箇所」は、左右一個ずつ伝搬していくので、kでない場所を探してから二分探索すれば良い。

 -1~+1、-2~+2、と伝搬範囲は広がるので、その範囲ずつ(つまり、大体2*ターン数)移動させていけば良い。

 ただし、詐欺師自身の値は変わらないこと、及び、最終的に変化しない状態になったとき、kの箇所は、詐欺師のものともう一つある可能性があることに注意しなくてはいけない。

 自分のシステムテストで落ちた実装は、n=4, k=2で、「2 3 2 1」となった最終状態でkでない箇所を探して、1と3を聞き続けるものになってしまっていた。2*ターン数ずつ移動させていたので、その二つしか聞けない!
 なので、移動する数を「1*ターン数」や「2*ターン数-1」に直したらACできました。

 なお、厳密にはこれでもHack caseがあるかもしれない。後半の二分探索パートでは、kなものを見つけたらただちにそれを答えとして出力するようにしているけど、「もう一つのk」がたまたま当たってしまうことはありそう。(本当にHack caseがあるかは分かってないです)
 多分、二分探索パートでkの箇所が見つかったとき、左右の数字も調べるようにすれば、そのケースも除外できるはず。

2021年1月5日火曜日

Codeforces Round #693 (Div. 3)

  pretestは全完でした。→システムテストも通っていました!


 ツイートだと読みにくいと思うので、Gだけ補足。

G. Moving to the Capital


Step1 都市1から各都市が何歩でいけるか(問題文中のdを)調べる。ここではDと書く。

Step2 それぞれの都市iから逆向きに一歩進んだ都市をjとして、ANS[i]=min(D[i],D[j])とする。高々一回だけ問題文中の2の移動をして良い、というルールを使った感じです。

Step3 各都市からDが大きい方へたどってみてANSの最小値が答え。問題文の1の移動でDが大きい方へは進めるので、これで答えが求まります。

 としました。

 Step3は再帰で書くのが簡単だと思う……けれど、CodeforcesのPyPyではこの深さの再帰は無理(エラーが出る)。(なお、AtCoderならエラーなしでいけるはずですが、PyPyの再帰は遅いので、間に合うかは分かりません)
 なので、どうしようか考えたけど、Dが大きい都市から見ていけば再帰なしでいけた。


2021年1月4日月曜日

東京海上日動 プログラミングコンテスト2020のD - Knapsack Queries on a tree

 東京海上日動 プログラミングコンテスト2020のD - Knapsack Queries on a tree

 を通したのでメモ。

 PyPyでは公式解説の方法ではTLEを取ることが不可能なようだ。(他の人の提出を見たけど、通せている人はいなそうだった)
 半分全列挙をlogを使わずにやるテクニックを用いると、PyPyでも通るらしい。(多分全ての)PyPyの提出はこの方法で通している。
 ……が、私が真似して書いてもなぜか通せなかった。

 ので、Kotlinで通そうと試みた。

 Kotlinだと、IntArrayなどを使って、最初から要素数を指定した配列で計算するのは速いが、mutableListOfとかで要素数を加えていこうとすると遅いようだ(良い方法があるのだろうか?)。
 なので、PyPyで速かった方法をそのままやろうとするとかえって遅くなる。
 そのため、公式解説の方法で、要素数を指定した配列のみを使ってやると通った。

 その言語での高速な書き方を理解していないと通せない問題は厳しい……。

2021年1月3日日曜日

AtCoder Beginner Contest 187

  年も変わったし、コンテストへの参加記録を書いていきます!
 ……といっても、コンテスト後、既に一日経っている。いつまで続けられるんでしょうか。


D - Choose Me

 何もしないと全員が青木氏に投票する。
 高橋氏が演説を行うと票がどれだけ動くか、と考えると、2*A+Bでソートすれば良いと分かる。

E - Through Path

 最初、全方位木DPが必要だと思い一旦飛ばした。
 Fを解いた後、やりたくないし書き終える自信がないけど、全方位木DPを書くかー、と書き始めたら、通常の木DPでいけることに気付いた。

 自然に書けば全方位木DP、「全体にxを足して、逆側からxずつ引く」ことを思いつけば普通の木DPでいける。

 とはいえ、全方位木DPでも書けるべき問題ですよね。

F - Close Group

 Nが小さいのでbit系を色々考えたが、しばらく思いつかず苦労した。
 「まず、完全グラフになっているものを列挙」を思いつけば、$4^N$のDPにはたどりつける。

 それが実は$3^N$になるというのは、この問題snukeさんの解説動画で覚えていた。難しい問題を解説ACしておいたのが役に立った! と嬉しかったのですが、Educational DP Contestのこの問題もそうだったのですね。解いたはずなのに全く覚えていなかった……。