ラベル とりあえず立式 の投稿を表示しています。 すべての投稿を表示
ラベル とりあえず立式 の投稿を表示しています。 すべての投稿を表示

2025年10月9日木曜日

Codeforces Round 1056 (Div. 2)

 Bまで二完。Cを考えていたら睡魔に襲われた。


C. The Ancient Wizards' Capes

 解説AC。
 実は答えが少ないのでは? とはコンテスト中も思ったのだが、どうしてそうなるかが分からなかった。
 コンテスト中、寝る直前に思ったことが正解で、式にしてみると良かった。

 右に立っている人を1、そうでない人を0とし、各A_iについて式を立てると、A_i-A_{i+1}を考えることで、i番目とi+1番目の関係性が出てくる。それを使えば、一番目が決まれば他全て決まることが分かる。

 として解けたけど、やっぱり結構難しく感じる。あまり速く解けるビジョンが湧かない。

D. Batteries

 自力AC。
 使った回数の和が少ない方から貪欲に投げていくとACできた。が、未証明。

 これでいけるのでは? と思うまで結構時間がかかった。Cを飛ばしていたとしても、こっちをACできたかは怪しい感じ。

2022年6月21日火曜日

東京海上日動プログラミングコンテスト2022(AtCoder Beginner Contest 256)

 Eまで五完。

コンテスト後のツイート

 F - Cumulative Cumulative Cumulative Sum

 解説AC。

 とりあえず立式すればその先が見えてくる問題。
 コンテスト中はこの問題を飛ばしてGへ行ったけれど、Fを考えていれば解けたかというと怪しい。

G - Black and White Stones

 解説AC。

 「各辺に置く黒い石の個数を固定したらDPで解けるのでそれを足し合わせれば良い」と言われれば難しくない。
 コンテスト中は、なんか上手い方法があるのではと思い、「ポリアの数え上げ定理」を調べたりして迷走していた。

 落ち着いて問題を見直してみると、特に対称性のある問題ではない。Dの制約が小さいことに注目して、各辺に置く黒い石の個数を固定して解けば良いのでは? と考えてみるべきだった。

2022年2月19日土曜日

yukicoder contest 332

 ACDの三完。


No.1842 Decimal Point

 このAの類題なんですね。当時解けてたのに忘れていた。
 立式の仕方はちょっと違うけど、「とりあえず立式」するしかない問題。

 ただ、
・$A*10^C$を$B$で割った式
・その余りを$10$で割った式(この余りが答え)

 の二式を書いても、直接、$A*10^C$を$B*10$で割れば良い……と分かるわけではなさそう?(これくらいしかできることはないとはいえ、若干の発想が必要な気がする)

 これに加えて、

・$A*10^C$を$B*10$で割った式

 を書き、見比べれば、この余りをBで割った余りが答えになることは示せる。

2021年8月27日金曜日

Codeforces Round #741 (Div. 2)

 途中寝かけたせいもあり、D1まででした。 


D2. Two Hundred Twenty One (hard version)

 D1で、さらに取り除くindexを指定するもの。
 D1で(証明しなかったけど)偶奇さえあっていれば答えが得られた。ということは、ある区間の長さが奇数ならば、一つ削除することで全体を0にすることができる。なので、区間が偶数で0でない場合は、どこでも一つ削除してから考えて良い。

 あとは、立式することが重要。区間[l : r]からxを削除するとすると、Sを累積和として、

・S[l : x-1]+S[x]+S[x+1 : r]=S[l : r]=kとする
・S[l : x-1]-S[x+1 : r]=0

 となれば良いので、計算すると、

・A[x]=1のときS[l : x]=k/2+1
・A[x]=-1のときS[l : x]=k/2-1

 となれば良いと分かる。

 なので、Sが条件を満たすもののうち、区間[l, r]に含まれるものを探せば良い。これは二分探索を使えばOK。


 なお、コンテスト終了間際に大体この考察には至っていたのだけど、その後ずっとACできなかったのは、「区間[l, r]に含まれるものを探す」という条件を忘れていたせいでした。[l, r]に含まれないものを出力して、なんでWAなんだろう……とずっと考えていた。ひどい。

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

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


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が一般の場合でも求められること

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

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

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をするだけなのだけど、その実装が難しくて戸惑った。

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


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

・まず、木の中心を見つける。(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年2月21日金曜日

TopCoder Single Round Match 776

 今年はじめてEasyがACできた! と思ったらシステムテストで落ちて0完。単純なミスだったのでもったいない。

Div1 Easy EncloseArea

 斜めに線を引いて、指定された面積の多角形を作る問題。

 最小の正方形の面積は2で、その一辺を削って、隣に膨らませれば面積は+2される。
 なので、まず50*50の方眼紙の対角線上に斜めに長い長方形を作り、それを一つずつ膨らませる実装をした。一々、辺を増やして減らして……としているためかなり汚い実装になってしまった。

 そして、一番最初、面積2から始めてそこから求める面積まで増やしていく……というように書いてしまったため、面積2のときそのまま出力するのを忘れてしまってシステムテスト落ち。

・最小値・最大値で通るかチェック

 はきちんとしなくてはいけませんね。

 kmjpさんのブログの記事のように頂点に着目すればもっと簡単に実装できます。

 コンテスト中のコードを修正したものなので非常に読みにくいのですが、一応ACしたコードを載せておきます(載せるか迷ったのですが、ACした証拠として)。

class EncloseArea():
    def enclose(self, A):
        if A%2!=0:
            return tuple()
        if A>2402:
            return tuple()

        ANS=[["."]*50 for i in range(50)]

        ANS[0][0]="/"
        ANS[0][1]="\\"
        ANS[1][0]="\\"
        ANS[1][1]="/"
        S=2

        for i in range(1,49):
            if S==A:
                break
            
            ANS[i][i]="."
            ANS[i+1][i+1]="/"
            ANS[i][i+1]=ANS[i+1][i]="\\"
            S+=2

        if S==A:
            A=[]

            for ans in ANS:
                A.append("".join(ans))

            return tuple(A)

        NOWP=[0,1]
        NOWM=[1,0]

        def rewrite1(x,y):
            if ANS[x-1][y]==".":
                ANS[x-1][y]="/"
            else:
                ANS[x-1][y]="."

            ANS[x][y]="."
            ANS[x-1][y+1]="\\"
            ANS[x][y+1]="/"

        def rewrite2(x,y):
            if ANS[x][y-1]==".":
                ANS[x][y-1]="/"
            else:
                ANS[x][y-1]="."

            ANS[x][y]="."
            ANS[x+1][y-1]="\\"
            ANS[x+1][y]="/"
            
        while True:
            x,y=NOWP
            if 0<=x-1<50 and 0<=y+1<50:
                rewrite1(x,y)
                S+=2
            NOWP[0]+=1
            NOWP[1]+=1

            if NOWP[0]==49 or NOWP[1]==49:
                MIN=min(NOWP)
                NOWP[0]-=MIN
                NOWP[1]-=MIN
                NOWP[1]+=2

            if S==A:
                break
                

            x,y=NOWM
            if 0<=x+1<50 and 0<=y-1<50:
                rewrite2(x,y)
                S+=2
            NOWM[0]+=1
            NOWM[1]+=1

            if NOWM[0]==49 or NOWM[1]==49:
                MIN=min(NOWM)
                NOWM[0]-=MIN
                NOWM[1]-=MIN
                NOWM[0]+=2

            if S==A:
                break

        if S==A:
            A=[]

            for ans in ANS:
                A.append("".join(ans))

            return tuple(A)

Div1 Medium StringRings

 kmjpさんのブログの記事を参考にして通した。

 両端赤、両端緑、両端が異なる、のそれぞれのひもの個数をr, g, bとおき、実際に本数を求めてみよう、と式を書いてみたら、自然と再帰的に書け(多分、C++なら再帰のままでもACできる?)、それを整理したらさらに簡単な式になった。

・とりあえず立式

 さえしていればそれほど難しくなかった気がする。

 コンテスト本番では、Easyに時間を取られてしまったので解けなかったけど、ACしたい問題でしたね~。

 以下、ACしたコード。

class StringRings():
    def expectedRings(self, A, B):
        ANS=0
        for i in range(A):
            ANS+=1.0/(2*i+1)

        for i in range(B):
            ANS+=1.0/(A*2+i+1)

        return ANS

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になっていました。半分全列挙しないと通らなくなったのかな。