KUPC 2020 Autmun 参加記

はじめに

KUPC 2020 AutumnにICPCチーム,state_of_the_art (sotanishy, renjyaku, mugen1337)で参加しました.
結果はABCDEFmの6完+m部分点でした.

f:id:MUGEN_1337:20201010235949p:plain
画像ではMが見切れています.

時系列

最近土日は大学図書館が開いていないのでWi-Fiを使えるよくわからない貸しスペースみたいなところに集まります.みんな5分遅刻して結果的に全員同時に集まります.
基本的に,いつもrenjyaku, sotaくん, 僕の順でmod3で問題を見ているので,今回もその作戦で行くことにしてKUPCスタートです!楽しみだなぁ~.
ちなみに僕が苦手な問題はパズル,数え上げです.

  • A
    renjyaku「書きます」

  • B
    sotaくん「解けました」
    よしよし.イイカンジですね.この調子で僕もCをね...

  • C
    Cをみましょう

    f:id:MUGEN_1337:20201011000525p:plain
    f:id:MUGEN_1337:20201011000554p:plain

    え?
    ちなみに,僕の苦手な問題はパスル,数え上げです.(2回目)
    困りました...

  • D
    「C,アカン.誰か任せた.(無責任)Dを見ます」

    ...

    「Dもアカン...パズルみを感じる...」

    帰っていい?
    まぁでもCよりは解けそうなので解きます.
    とりあえず偶数は自明に作れるのでどうでもいいですね.
    Bを書き終わったsotaくんと一緒に考えると,平方数の倍数は作れそう.ということに気づきます.
    1, 3, 5
    9, 11, 7
    17, 13, 15
    という流れでn=9が作れることに気づき,同じように1ずつずらして並べることで平方数は作れます.
    ここら辺で,もうそろそろ十分じゃない?1回落ちてから考えない?という話し合いをして平方数の倍数なら作ってそれ以外はimpossibleな実装をします.
    落ちます.(悲しい)
    ある程度をx2のブロックを何個か並べて作って,そのあとに残った個数がxの倍数かつ,偶数倍ならいいことに気づきます.
    ここらへんでもう十分じゃない?落ちたらまたその時考えない?ということにしてまた実装します. 通ります.(嬉しい)
    こんな通し方でいいのか...

  • E
    renjyakuをCに据え置きにして,僕とsotaくんで先に進むことにします.
    僕がDを書いている間にEを読んでくれたsotaくんから,DPじゃないか,という案をもらいます.
    僕も読みます.「DPやんけ(単純)」
    さぁ遷移がまだ二乗なので落とすぞと考えます.
    ...
    無理.
    悲しいね.もはやこれまで.
    ここらへんで僕が「二分探索じゃね?」「いや二分探索してもDPの遷移変わらないし嬉しくねぇ~」を毎秒30回言うようになります.
    ところがどっこい,自明なDPと二分探索を組み合わせると,値でDPしていたのがbool値でDPできるようになり,(雰囲気伝われ),イイカンジにイイ感じにできることに気づきます.
    僕が書いて無事AC.
    DPにしか見えないけどそんなことないとてもおもしろい問題に感じました.好きです.

  • F
    見た瞬間,僕が「不可能」と叫びます.
    でも,僕が広義単調増加の文字を見落としており(太字で書いてあるやが)右下に点を拾っていくだけでいいことをチームメイトが教えてくれます.
    僕が「可能」と叫びます.
    おおまかな方針を立てて細部詰め切れずに放置して一回順位表と他の問題を見ていると,Cで無事満点を取って帰ってきたrenjyakuが詰めてくれました.え,すごい.
    僕が引き継いでAC.感動です.

  • G
    Fでちょっと詰まったタイミングで,Gを僕が読みました.
    ...
    僕の苦手な問題はパズル,数え上げです(3回目)
    この位置に置いてある数え上げ,無理みがすごい.放置.

  • M
    僕とrenjyakuがFをわちゃわちゃしている間に,Mをsotaくんが取り掛かっていました.
    sotaくん「OEISを使うと,カタラン数がでてきました.3乗は自明に書けました」
    え,チームメイト,優秀...
    sotaくん「部分点とりあえず欲しいですね,3乗から2乗に落としたいです.この遷移,畳み込みですか?NTT使えますか?」
    ぼく「はい.そうだと思います.」
    え,チームメイト,優秀...
    無事部分点獲得!

    やっぱりMがここまで考えられたら解きたいですね.ということになり,Mをみんなで考えます.

    ここらへんで僕もちゃんと脳が走って仕事をします.
    ぼく「もしかしてK項目を0にしたカタラン数を係数とした多項式のN乗になっていませんか?それ」
    と,有効な考察をひねり出すことに成功します.
    でも多項式をN乗するなんて無理ではないですか?
    巷でよく聞くFPSとかいうのでうんぬんかんぬんをするとうまくいくのかなぁとか思いますが,履修していません
    sotaくん「じゃあ繰り返し自乗できますね」
    え,チームメイト,優秀...

    もう完全にお祭りムードです.
    KUPC最高!一番好きなKUPCです!
    やったぜ!最高だ!空も飛べるはず

    落ちます.え? 1e6にlog2個はダメですか...そうですか...

    終了...

コンテスト終了

MはやっぱりFPSでできるみたいですね
多項式のpowはlogが1個落ちてlog1個でできることをコンテスト終了後に知りました.びっくりです.
まぁライブラリは仕組みがわからなくてもどのライブラリを使えば何ができる,というのを一通り知っておくのもいいかもしれないですね.

まぁでもチーム戦でらしいことができたのでよかったです.
最高に楽しかったです.
やはりKUPCいい問題が多いと思います.
EFは特に解けたとき「あ~~~おもしっれ~~~今,俺は生きているぜ~~」となりました.
運営の皆さん.本当に楽しいコンテストをありがとうございます.

yukicoder No. 1233 割り切れない気持ち 解説

なんかどっかでみた

解法ローラーをします.

式変形->modがあるのでかなりきびしい
もうない

制約をよくみます

a_i<=2e5,意味ありげですね.

とりあえず,入力を受け取って以下のようにします.
b[i]=(aの中で値がiとなる要素の個数)
累積和で以下を取れるようにします.
s1[l~r] : b[l]+...+b[r]
s2[l~r] : b[l]*l+...+b[r]*r

for i=1 to 200000
  for j=i to 200000以上のどっか, j+=i
    res+=b[i]*(s2[j-i~j-1] - s1[j-i~j-1] * (j-i) )

で終わりです.

計算量

調和級数のアレで二重ループはnlogn,結果O(nlogn)

なんでこれでいいの?

f:id:MUGEN_1337:20200918230646p:plain
こういう例を考えます

a[5]=5で他の要素をmodとって総和を数えることを考えます.
この時,幅5でシフトしていって和をとっていくことを考えると上の式がしっくりいくと思います
[0, 4]のところの和をとります.i*b[i]の和をとればいいので4です
[5, 9]の所の和をとります10+7-3*5で2です.なんでかというと,いま見ている幅5~9は5で割ると商は1だから,modをとるとそれぞれ-5されるからです.

これを考慮すると調和級数がぴったりはまって気持ちがいいですね.

提出

yukicoder.me

かわいい

おまるぽるかかわいくない?

黄色になりました

嬉しい

f:id:MUGEN_1337:20200914115055p:plain

統計

  • AC数
    Rating Historyより
    Topcoder : 0
    Codeforces : 580
    AtCoder : 1512
    AOJ : 71
    yukicoder : 338
    library-checker : 17
    Sum : 2518

    AtCoder RPS : 323900 f:id:MUGEN_1337:20200914120520p:plain


  • 真面目にやってたのは1年半くらい(アカウントだけ作ってやっていなかった時期があった)

  • 他コンテストサイト
    Codeforces : 2197

青になってからやったこと

競技プログラミング,食事,家事,研究,北海道旅行,Apex Legend,バイクの点検,ランニング,VTuberにはまる,など
何が効果的でレートが伸びたのか,よくわかりません.ただ,水色の時とかに比べて,とにかく難しい問題を諦めないで頑張る,解けなかった問題は解説をみて解くだけじゃなくて,じゃあ今どういう準備をしたら次みたときに解けるようになるか,を意識しました.後者に関してはニッチなライブラリを作っておく,も入ります.
まぁでもとにかくやめないことが大事だと思います.やめたらレートが伸びないので.

謝辞

僕が黄色になるまで本当にいろんな人にお世話になりました.ツイッターでお祝いメッセージもいっぱいいただきましたが,照れ臭くてなかなか1人ずつちゃんと返せなかったのでここでお礼を申し上げます.
競プロを知るきっかけとなり,初心者のころたくさんを教えてくれたゆきのん,サークルpuzzleknotのメンバーをはじめとする弊大学の競プロerの方々,ついったーのFFの方々,本当に挙げればキリがありません.
本当にいつもありがとうございます.みなさんが競プロを頑張っているから僕も頑張れます.これからもよろしくお願いします.

おわりに

黄色になるという目標があまりにもデカくて,達成した今,これから今まで通りの熱意で競プロを続けられる気がしないのがまずいです.とりあえず研究をほったらかして競プロをしていたのでそろそろ研究をします.悲しい.
あと,もし研究室配属を控えている競プロerがこれを読んでいたら聞いて欲しいのですが,マジで配属される前にやれるだけ精進しておいた方がいいと思います.正直研究室配属されてから精進に取れる時間がめっきり減りました.特に弊大学は結構厳しい研究室多いんじゃないでしょうか.まじうんち.

なにはともあれ,また色変記事を書く日が来ればいいなぁ...もう来ないかも.

Codeforces 393 C Nikita and stack

問題概要

codeforces.com

stackの操作列が順次与えられます.それまでに与えられた操作列を適用したときのtopを求めてください 操作は(順番, 操作)で与えられます.うまく説明できないので下に例を出します
入力
(2, push1)
(3, pop)
(1, push2)
(4, push3)

1個目が飛んできます.操作列は(push 1)のみです.よってtopは1なので1を出力します.
2個目が飛んできます.操作列は(push1)(pop)です.よってstackは空なので-1を出力します.
3個目が飛んできます.順番通りに操作を並べるので,操作列は(push2)(push1)(pop)です.よってtopは2なので2を出力します.
4個目が飛んできます.操作列は(push2)(push1)(pop)(push3)なので出力は3です.
よって,
1
-1
2
3
を出力すればいいです.
また,スタックが空の時にpopが来ても,何もしないこととします.

解法

先に,自分がバチャ中にとった解法を説明して,なぜこの解法を思いつくに至ったかを説明しようと思います.

平方分割をします.
操作列をブロックに分割して,最後にそれらをマージすることを考えます.

ちなみに,O(nlogn)で済むセグ木解が想定だし,実装も少なそうです.
ですが,個人的には平方分割が考察が少なくてすぐ思いつけたので,一応記事を残しておこうと思います.

各分割で何を持つか

各分割の操作を要約することを考えると,結局,何個かの要素をpopしたあとに数をいくつかpushする,という操作に要約できます.
以下に例を示します.
(push3)(pop)(pop)(push1)(push3)(pop)(push4)
という操作は,相殺される部分を考慮すると,1回popしたあとに[1, 4]をpushする,というように言い換えることができます.
このように,ブロックbについて,先にpopする回数k_b,そのあとpushする数p_b=[p_b0, p_b1, ...]を管理します.
これで,クエリが飛んでくるたびに,更新がいるのは1ブロックだけなので,更新にいるのはO(sqrt(n))です.

各分割をマージしてスタックトップを求める

では,これらのブロックからスタックトップを求めることを考えましょう.
こちらは考えてみると意外と簡単で,スタックの気持ちになると操作列の後ろの方から考えてあげるのが自然です.
例を示しましょう.
ブロック1 : k=3, p=[2,3,4]
ブロック2 : k=2, p=[6,1,8]
のような分割をしたときに,スタックトップは8です(それはそう).最後にpushするのが8なわけですから.では,
ブロック1 : k=3, p=[2,3,4]
ブロック2 : k=2, p=[ ]
の時はどうでしょう.この時,スタックトップは2になります.操作は,(pop)x3, (push 2, 3, 4), (pop)x2になるからです.
最後にダメ押しの1例を考えます.
ブロック1 : k=3, p=[2,3,4]
ブロック2 : k=1, p=[5,6,7]
ブロック3 : k=4, p=[ ]
この時はどうなるでしょう.
この時,スタックトップは2になります.後ろからみていくとわかりやすいです.
結局,後ろからスタックを掘る数を累積していって,掘り切れなくなったところが答えです(日本語力がないから説明がヘタクソ)
そして,この時,pの中身をいちいち見る必要がないので,各ブロックからスタックトップを求めるのもO(sqrt(n))でできます.

結果,操作が飛んでくるたびに,分割の1ブロックの更新,全ブロックからスタックトップを求める,の両方がO(sqrt(n))でできるので,この問題をO(n sqrt(n))で解くことができました.

平方分割でやろう,という気になると後の考察に難しいポイントはあまりないような気がします.

じゃあどうやって思いつくの?

こういうのはどうやって思いつけばいいのかが大事だと思います.
僕がとった考察の流れとしては,
1. セグ木っぽい.操作列をセグ木で持って,操作が飛んでくるたびに,何かを更新してO(nlogn)で解けないか.
2. セグ木でいい方法があまり思いつかない.というか,セグ木である必要ある?毎回求めるのは全体の操作列の後のスタックトップ,部分的な解がいるならセグ木じゃないとだけど
3. じゃあ平方分割考えてみるか.意外と簡単にできそう?
という流れでした.

おまけ

平方分割,解いたことがないとどのような実装をすればいいのかわからないと思います(僕がそうだったため).
僕が意識しているコツとしては,
1. ブロックのサイズは問題の制約をみて固定する,入力に依存する形にしない.この問題ではn<=1e5だったので僕は400に固定しました.
2. ブロックの更新はできるだけサボる.ブロックの更新も必要な部分だけやろうとすると大変なことが多いので,思い切ってリセット.実装を後に載せますが,この問題では,更新のたびに,上でいうpをクリアしてもう1回全部求めなおしています.この方が圧倒的に楽だと思います.

実装です.
codeforces.com

最後に

僕は頭があまりよくないので,平方分割をスムーズにできるようになるまで大分時間を要しました.
なのでこの問題を割とスムーズにしばけたのは嬉しかったです.
と,いうかセグ木で考え出してセグ木諦めて平方分割で解きます->セグ木が想定でした.なの,弱い.悲しい

近況

色変かけてコンテスト出るの疲れる.早く楽にしてくれ ネタバレを大量に含みます

最近面白いと思ったの

多分どれも青上位から黄色くらいの難易度

クローゼットの配置

F - クローゼットの配置

最大長方形の類題と聞いてかかってみたが,とても詰めるのに時間がかかった.

Decypher the String

Problem - E - Codeforces

とてもいいインタラクティブの演習になった. 使わなくても解けるらしいが,自分はgarnerのアルゴリズムを初運用できてとても嬉しい.

Trucks and Cities

Problem - F - Codeforces

ダブリング久しぶりにみて嬉しい気持ちになった

(Zero XOR Subset)-less

Problem - G - Codeforces

xorの基底.いやこれはマジで好き.すげぇ好き.

夕食

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2642&lang=jp

全人類解くべきでは?最高に気持ちがよかった.人が競プロをやる理由.星が大量につく理由がわかる.

Replace

No.1194 Replace - yukicoder

グラフをイメージ->フローかな?->SCCでいいね.の流れで考えたけど,とても好き. これ綺麗だなぁ.どうやったらこんな問題作れるん?ほんま好き.

あなたの名は

No.482 あなたの名は - yukicoder

yukicoでうしさんの名前で問題検索しながら解いて出会った.置換の偶奇性好きなんだよなぁ~~

おわりに

もし黄色になったら抜け殻になる気がする.

Educational Codeforces Round 72

はじめに

バチャを走りました.Dまでの4完,517位相当
コンテスト後にEも通しました
f:id:MUGEN_1337:20200506142842p:plain
とても楽しいセットだったので記録を残します

A. Creating a Character

問題

キャラを作ります.初期のstrengthはstrでintelligenceはintです.今experience pointをexp点持っています.これを1使うとstrかintを1上げれます.最終的にstr>intとなるようにポイントを全部振るようなパターン数は何通りですか.

解法

strに振るポイントをaとすると,intに振られるポイントはexp-aであり,ここからaの下限が求まります.a>(exp-(str-int))/2です.これを使って解けます.Aにしては難しくないですか.

B. Zmei Gorynich

問題

HPがxの敵を殺したいです.n個の行動の選択しがあって,好きな順で好きなだけ行えます.i番目の行動を選ぶと,敵にダメージdiだけ与え,そののち敵はhiだけ回復します.最短何回の行動で敵を殺せますか?殺せないなら-1.

解法

とりあえずdi-hiの値が大きいやつで最初はHPを削り,そしてとどめをhiに関係なくdiが大きい行動でやるとよいことは明らかです.これは二分探索がやりやすそうです.二分探索で殺せる最小の行動回数を探索しました.check(m)をm回の行動で殺せるか,にし,m-1回di-hiが最大の行動をし,1回diが最大のでダメージを与え,合計ダメージがxを超えるかで判定してあげるとできました.
今書いてて気づいたんですが,別にceil( (x-max(di) ) / max(di-hi))でよくないですか?
頭ついてなかった...

C. The Number Of Substrings

問題

01の文字列が与えられます.連続な部分文字列の長さとその部分文字列を2進数としてみたときの値が一致する部分文字列を数え上げてください.

解法

こういう条件を満たす部分列を数え上げるみたいなのはABCでよく見る気がします.最近特に.
しかしそのような問題と違い,今回は単純なしゃくとりなどはできないことがわかります.そしてコンテスト中はSuffix Arrayの上での二分探索を使いACしました.自分のSuffix Arrayは二分探索を実装していなかったのでライブラリを窃盗しました...
Suffix順に並んだ配列で,どこからどこまで接頭辞が同じかを二分探索で求めることができるのでこのような問題を殴ることができますね
codeforces.com

D. Coloring Edges

問題

n頂点m辺の有向グラフが与えられます.どのサイクルも一色だけで塗られていることのないように塗るには最低何色いりますか?塗り方の復元もしてください.

解法

超いい問題だった.感動した.この問題をみんなが楽しめば世界から戦争はなくなる.
まず,DAGなら1色で彩色可能なのは明らかです.だってサイクルがないのですから.
そして問題文の雰囲気的に強連結成分分解をします.そうすると強連結成分ごとに問題を解けばいいことが分かります.強連結成分同士をつなぐような辺はテキトーに1で塗っておきましょう.
では,強連結成分をどのように彩色すればよいかを考えます.結果からいうと,強連結成分の頂点を一列に並べ,左から右へ行くような辺を色1で塗り,右から左へ行くような辺を色2で塗るのが一種の最適な彩色方法です.
このようにすると色1だけのサイクル,色2だけのサイクルは絶対にできません.
じゃあどのように実装するかですが,dfsをし,頂点の訪問順ordを記録しておき,同じ強連結成分内でord[from]<ord[to]なら色1をそれ以外なら2を割り当てることで先述の彩色を実現できました.
これ楽しいですね.最近lowlinkとか二重辺連結成分とかグラフまわりを色々勉強してたおかげで解法がすぐみえて楽しかったです.まぁグラフが連結とは限らないことを忘れて1点からしかdfsしないで30分くらい溶かしたのですが...

codeforces.com

E. Sum Queries?

問題

とても長いので省略

解法

セグ木以外何もみえない...
2個より多くでサブセットを作っても得しないし,同じ桁が0じゃない2つでサブセットを作れるので,2個でサブセットを作りたい.
桁ごとにセグ木持って,セグ木の値はa[i]の桁が0ならINF,それ以外ならa[i]をもって最小値と二番目に小さい値を持つようなセグ木で桁ごとにしばく
神に祈りながらセグ木を10本持ちます...

codeforces.com

祈りは通じました.

おわりに

楽しい...楽しくない?楽しいです.
Fはみていません(猛省)

Codeforces 630 F Independent Set

Codeforces 630 F Independent Set

はじめに

マジでE問題場合分けを忘れて全てを破滅させた.ドドドドドドドちくしょう
そしてこの問題を頑張って解きましたが,コードは汚く,考え方もスマートであるととても思えないのでもっといい方針があるのだろうと思います.

問題概要

https://codeforces.com/contest/1332/problem/F
木の空ではない全ての辺集合について,辺集合の辺と全ての辺が結ぶ頂点からなる部分グラフを作り,独立集合の個数を求め,総和を求める.

考えたこと

 まずこの問題を読み終えたときに,「木の頂点を白黒二色で塗るとき,辺の両端が黒にならないように塗る塗り方は何通り?」みたいな問題を昔やったような気がして,それを思い出していました.その問題は各頂点ごと,その頂点を黒で塗る場合と白で塗る場合で数え上げる木DPで解いた気がします.
 ですので,この問題も似たような方針がとれないか検討しました.紆余曲折あったあと,この問題でも独立集合を黒が隣り合わないような頂点の塗り方と言い換え,各頂点について,その頂点が,黒である,白である,使わない,それぞれの場合のその頂点以下の部分木においての題意を満たす場合の数について数え上げれるんじゃないかという考えになりました.
そして遷移を一億年ほど考えた結果解けました

f:id:MUGEN_1337:20200402115602p:plain

以下プログラム同様,各頂点vについて,自分を黒く塗るときの自分以下の部分木を塗る場合の数をv.black,自分を白く塗るときのそれをv.white,自分を除くときのそれをv.remとして軽く説明します.

自分を黒く塗る場合の数

f:id:MUGEN_1337:20200402121138j:plain
各頂点から子に対して画像のような関係があり得ます.左から,
- 自分と子が繋がっていて,子を白く塗る ch.white
- 自分と子が繋がっていて,子の子はない ch.rem
- 自分と子が繋がっていて,それで終わり 1
- 自分と子が繋がっていない ch.white+ch.black+ch.rem
ここで注意なのは,左から3番目のような場合の数はch.remには含まれていないため+1を忘れずにしてあげることです.なぜならch.remにはその子を使わないような子以下の部分木で題意を満たすような集合の個数を数え上げています.ですので頂点が1つだけポツンとあるような場合は辺が1本もないため,題意を満たすような集合ではないため,別で数えてあげる必要があります.
よって,それぞれの子についてこれらの状態の場合の数の和の総積をとると自分を黒く塗るような場合の数が計算できそうです.
ところで,これだと以下のような自分だけ1個で孤立するような状態を数えてしまうので引きます
f:id:MUGEN_1337:20200402115347j:plain

自分を白く塗る場合の数

おんなじ感じで頑張ります

自分を使わないとき

ch.black+ch.white+ch.rem+1の総積-1
ch以下を全く使わない場合を考慮して+1,一つも何も使わない空集合を後から引くから-1

おわりに

 途中からわかりやすく説明するの無理じゃんとなって書く気が減退しました