ABC238 参加記録
コンテスト中AC: A〜D
A - Exponential or Quadratic
から、が64bit整数型に収まらなければ、直ちにYesです。
よって、64bit型を超えるまでは、実際に2を掛けていきます。そして、超えた時点でYesとします。
超えなかった場合は、実際に比較してみれば良いです。
2を掛ける最大回数は、おそらく64くらいだと思います。
オーバーフローの判定はC++(GCC?)なら、__builtin_sumlll_overflow
でできます。
提出コード
D - AND and SUM
という風に表記したとき、2進数の下からiビット目とします。
の時、とするしかありません。
の時、の3パターンがあり得ます。少なくとも一方は0にする必要があり、それはどちらでも良いので、と仮定しておきます。この時、となります。
及び桁上がりによって、以下のように下の桁から順に、を決定、もしくは、達成可能か決定できます。
の場合
このケースではで、桁上がりが必ず発生します。の場合
と桁上がり1を計算すると、1+1+1 = 11になります。- の場合、計算結果と一致しないのでNoです。
- の場合、矛盾しないので問題ないです。
の場合
と桁上がり0を計算すると、1+1+0 = 10になります。- の場合、矛盾しないので問題ないです。
- の場合、計算結果と一致しないのでNoです。
このケースではです。の場合
- の場合、とします。0+1+1=10で、桁上がりが発生します。
- の場合、とします。0+0+1=01です。
の場合
- の場合、とします。0+0+0=00です。
- の場合、とします。0+1+0=01です。
なお、十分に大きい桁(今回なら制約上60桁目)まで、上記処理を行った後、桁上がりが残っていた場合もNoになります。
公式解説メモ
より、
より、を満たしていなければNo
とおく。
という連立方程式を解く問題となる。桁上がりは不要で、各bitのみを見れば良い。
となるbitがあると、達成できずNoとなる。
よって、となるかどうかで判定ができる。
連立方程式の解き方
を仮定すると、より、両辺のa XORを取ると、
となるので、より、
を満たせば良いです。
公式解説は、の組が4通りの考慮が必要で、更にそこからa AND b = 0という条件を導き出さないといけないので、それよりは多少楽かなと思いました。
ABC237E Skiing
コンテスト中にACしたものの、after_contestでTLEで、嘘解法でした。 この記事は公式解説を自分用にまとめたもので、新たな情報などは無いです。
ここでは、標高が低いところから高いところに行く坂を下り坂、逆を上り坂と言うことにします。
2個目の条件を、
X→Yが登り坂なら楽しさが増加する
と読み替えておきます。
例として、広場1から5へ移動した時の楽しさを考えます。下り坂なら上り坂なら、とします。
ここから、広場1から5への楽しさはすべて、
- という形式をとり、は経路によらず一定
- という上り坂を取るたびに、にが足される
ことがわかります。
よって、を最大化したい時、を最小化すればよいことになります。
そのため、
- 下り坂なら、にが足される
- 上り坂なら、広場XからYに移動する場合、にが足される
というようなグラフを考え、広場1から5までの最短経路を求めればよいことになります。
この時の最小コストをとすれば、の最大値は、
として求められます。
上記議論は広場5のみならず、一般の広場でも適用できることが予想できます。そのため、先程の言い換えたグラフにおいて、広場1から、各広場への最短経路を求めると、その広場での楽しさの最大値は、
という風に求められ、答えとなる全体での最大値はこれらのmaxを取れば良いと言うことになります。
なお、このグラフのコストは全て非負なのでダイクストラ法が適用でき、で最短経路を求められます。
ポテンシャル
テキストの公式解説ではポテンシャルで説明されていました。
各頂点にポテンシャルを定め、uからvへの有向辺のコストをに変換します。
例えば、からへの変換後における経路が、
であった時、からへのコストの和は、
です。変換前のコストの和は、
なので、
つまり、
は経路によらず一定なので、を最小化すればも最小化されます。よって、変換後の最短経路問題を解けば良いということになります。
これの何が良いかと言えば、負辺を含むグラフについて、ポテンシャルを適切に定めることで、負辺を消してダイクストラ法を適用可能にできるということです。
蟻本によれば、フロー問題で複数回最短経路を求めたいときに最初1回だけベルマンフォード法で最短路を求め、最短路をポテンシャルに設定すると、負辺が消え、以降はダイクストラ法を適用できるようになって計算量が落とせるということでした。
AtCoderの問題でポテンシャルを使用することは無いような気もしますが、今回のように
- 各頂点に何らかの値が設定されている
- 負辺がある
場合にこのような考え方ができないか検討してみるのも良いかと思いました。
ABC237 参加記録
コンテスト中AC:A〜E
Eは嘘解法(Cも若干そうでしたが)だったので、ここでは省略します。
C - kasaka
先頭から連続しているaの個数をhead、末尾から連続しているaの個数をtailとします。
先頭に付け加えるのに必要な個数は、tail - headです。この分だけ付け加えて回文かどうかを判定することで答えを得られます。
参加記録を書くにあたって、実装を見直したところ、何故か末尾の方が少なかった場合に、末尾にもaを追加する実装になっていました(嘘解法でした)。ただ、付け加える個数をtail - headのままにしていたのでACとなっていました。
提出コードは、正しい解法で書き直したものです。
提出コード
D - LR insertion
双方向連結リストにより、挿入操作をでできます。
:数列において、の後にある整数
:数列において、の前にある整数
但し、前または後に何もなければ-1とします。
各挿入操作は以下のようになります。
の場合
- iの前はi-1なので、
- i-1の後ろはiとなるので、
- i-1の後ろにあったものがiの後ろとなるので、
- i-1の後ろに整数xがある場合()、xの前はiとなるので、
の場合
- i-1の前はiとなるので、
- iの後ろはi-1となるので、
- i-1の前にあったものがiの前となるので、
- i-1の前に整数xがある場合()、xの後ろはiとなるので、
答えの列は、を辿って先頭まで行き、を辿って末尾まで行くことで、得ることができます。
提出コード
ARC134 参加記録
コンテスト中AC:A,B
Cはコンテスト後自力AC
A - Bridge and Sheets
便宜上、を追加しておきます。
最初からかけられているシートのうち、最も右端をとします。最初としておきます。
にかけられたシートを考えるとき、前のシートの右端からまでがシートがかけられていない区間です。
これより、シートがの長さ分必要になるので、がこの区間で必要なシートの枚数です。なお、シートが重なっていた場合に長さが負になってしまうので、0とmaxをとっています。
のシートの右端は、なので、と更新します。
この操作をi = 1からN+1まで繰り返し、シートの枚数を足し合わせていけば、それが答えです。
提出コード
B - Reserve or Reverse
交換の優先度はインデックスが小さい方が高いので、交換するかどうかをi = 1から順に考えます。 と交換する対象は以下の条件を満たすようなものを貪欲に決定できます。
- 辞書順でより小さい
- 辞書順で最も小さい
- であるを既に交換していた場合、である
- 最も後ろに出現する
1は自明だと思います。
2ですが、1を満たす中では、辞書順でできるだけ小さい文字を持ってきた方が全体の辞書順を小さくできます。
3ですが、これまで交換対象としたものより、内側である必要があります。(2と8既にを交換していたら、3〜7の中から選ぶ必要がある)
4ですが、3の条件からわかるように次以降に選ぶ交換対象は今選んだものの内側になってしまいます。そこで、1,2の条件を同時に満たす文字が複数個あった場合、次以降の候補をできるだけ増やす為に、できるだけ出現位置が後ろのものを選ぶのが良いです。
条件を満たすものを得る方法ですが、Segment Treeが使えます。
例えば、AtCoder Libraryのsegtreeであれば、の組を記録し、二項演算op、単位元eを以下のように定めておきます。
- :なら、小さい方を返す。そうでなければ、の大きい方を返す。
単位元はなどとしておけば良いです。
提出コード
こちらの実装では、交換対象を左端と右端から考えていくイメージで、を右側で交換した中で最も小さいものとして記録しています。
C - The Majority
条件を満たす箱の入れ方の一つを例にしてみます。
箱1:1 1 1 1 2 2 2
箱2:1 1 1 3 3
箱3:1 1 4
箱4:1
ここから、
- 1以外の数字があれば、必ずそれに対応する1が存在する
- 各箱につきペアとなった1を除いた1が少なくとも1つ存在する
ことが言えます。
1の個数をとし、1以外の個数をと名づけます。
1つ目の考察から、が成り立つ必要があるので、これが成り立たない時、0通りです。成り立つ場合、ペアを作るので、その分の1の個数を減らして、
としておきます。
2つ目の考察から、ペア以外の1を各箱に少なくとも1つ入れないといけないので、予め各箱に1つずつ分配しておきます。従って、である必要があります。これが成り立たないとき、0通りです。
成り立つ場合、1の個数を減らして、
とします。
後は、1の残り及び、1とペアになった各数字を箱にいれていきます。1やペアは区別がないので、これを区別のある箱に入れる組み合わせは、重複組み合わせです。1または1とペアの各数字の個数を個とすると、
です。
これを各数字について求めていくわけですが、
なので、で分子分母が求まります。
分母は、の乗法逆元を求める必要がありますが、フェルマーの小定理に基づいてで求めることができます。
ある数字の入れ方それぞれについて他の数字の入れ方が存在するので、上で求めたものの総積を求めると答えになります。
計算量は、各数字について重複組み合わせを計算する必要があるので、です(多分)。
提出コード
ARC133 参加記録
コンテスト中AC:A,B
A - Erase by Value
同じ数は消えるので、一旦連続して重複した数について、重複を削除して考えます。
から始めて、単調増加している間は、消すと辞書順では大きくなってしまうので、消さない方がよいです。
そこで、減少に転じた時に、その直前の数(すなわち、を開始点とした、単調増加な部分の最大値)を消してみます。この時消す数をとし、生成される数列をとします。
すると、より後の数を消すと、必ずという部分が残るので、この時生成される数列がより辞書順で小さくなることはあり得ません。よって、が辞書順最小となります。
なお、減少に転じない場合もあるので、その場合は末尾の数を削除対象にします。
[提出コード] (https://atcoder.jp/contests/arc133/submissions/28684574)
B - Dividing Subsequence
を、それぞれとすることをとを対応させると呼ぶことにします。
また、問題文の制約に沿って生成されるを適合列と呼ぶことにします。適合列の要素数と言った時には (または)の要素数を指すことにします。
とを対応させた時に得られる適合列の要素数の最大値は、
- から作られる適合列の内、要素数が最大であるもの + 1
です。
添字に着目すると、かつを満たすすべてのの適合列の要素数の最大値が計算されていれば、のときの最大値を計算できます。そこで、添字の問題に帰着させます。
とが対応可能である場合にとなる配列を作ることを考えます。
とのあり得るすべての組をに記録します。
例),
はと対応可能
はと対応可能
はと対応可能
はと対応可能
の構築方法は後述します。一見全組を得るのにになりそうですが、エラトステネスの篩と同じ様にで構成できます。
をキーとして昇順ソートします。すると、ならになります。
一旦、の場合を無視すると、
- を満たす (つまり、より前に出現したもの)のうち、となるから最大値を得る
問題となります。これはBynary Indexed Treeによって反転数を求める問題の考え方を応用できます。
今回は最大値を得たいので、maxの二項演算が規定されたSegment Treeを使います。Segment Treeの値を保持する配列をとします。最初、全て0です。
を計算したいとき、という計算によって、となるから最大値を得ることができます。
そして、
と更新すれば良いです。の場合ですが、今回はこれを満たすは無視したいです。そこで、が等しいものについて最大値を全て計算し終わった時点で、一括して上記の更新を行うようにすれば良いです。
上に示した方法によって、の小さい方から順に最大値を計算して行くことができます。
さて、後回しにしていた数列の構築方法です。
配列を用意し、が出現したインデックスを記録します()。
例)
の時、
と対応可能なはの倍数です。よって、この時のを得たい場合、配列を利用すると、
というように得ることができます。
これはエラトステネスの篩と似たような処理であり、です。よって、それぞれの配列サイズもで収まると言えます。
全体の計算量は、の構築にで、最大値の計算と更新にもとなるので、だと思います。
提出コード
ちなみに、上記コードはインデックスの組を二次元配列として持たせています。
iと組が作れるjは、
ABC236 参加記録
コンテスト中AC:A〜C
D - Dance
解けなかったので、反省点含めて記録します。
まず、Nが小さいことから全探索が可能と予想して、全状態数を見積ってみました。
例えば、N = 3のとき、{1,1,2,2,3,3}を並べ替え、人{1,2,3,4,5,6}に対応させ、同じ数字を2人組にするという方法を考えました。
{1,2,2,3,1,3}なら、{1,5},{2,3},{4,6}という具合です。
これは同じ物を含む並び替えなので、
が並び替えの総数です。N=8の時を計算すると、81,729,648,000通りとなり全探索は無理そうだという結論になりました。しかし、これは誤りです。
例えば、N=2のとき、以下のように全探索できて、楽しさが異なるような組の作り方は3通りになります。
人1と同じ組になる人を全探索する。
{1,2},{1,3},{1,4}のいずれかとなる。残りの組を決める
- {1,2}のとき、{3,4}を組にしてを計算する
- {1,3}のとき、{2,4}を組にしてを計算する
- {1,4}のとき、{2,3}を組にするを計算する
最初の見積りだと、6通りになっています。何がまずかったかというと、組の作る順序を考慮してしまっています。言い換えると、組に番号をつけて区別してしまっています。
最初の見積りだと、{1,2,2,3,1,3}と{3,1,1,2,3,2}は区別されます。しかし、排他的論理和は順序によらず同じになるので、これらを同一視できます。
ボールと箱の問題に言い換えてみると、最初の見積もり方法は、
- 区別のある箱に区別のあるボールを2個ずついれる方法の総数
正しい見積もりは、
- 区別のない箱に区別のあるボールを2個ずついれる方法の総数
です。
従って、正しい見積もりは、各組の選び方に対して組に番号をふる方法が通りあるので、これを除いた
です(多分)。
N=8のとき、2,027,025通りとなり、これで全探索可能ということがわかりました。
実装に移ります。
全探索ですが、同じ組の組み合わせを作らないようにする必要があります。これは典型とも言えるかも知れませんが、小さい順に決定していくと上手くいきます。具体的には、
1. 組を作っていない人の中で番号が最も小さい人を選ぶ
2. 人と組を作れる人全員について、それぞれの人と組を作り、それぞれの場合について1に戻る
となります。この様に状態が複雑に変化してfor文で処理しきれない場合はDFSが有効です。
一応、実装を載せておきますが、無駄に長く、ややこしい実装になってしまったので、他の人を参考にした方が良いです。
提出コード
ここからは直接問題とは関係の無い内容となります。
状態遷移を伴うDFS
状態遷移を伴うDFSは苦手だったので、今後のために実装方針をまとめておきます。
個人の見解なので、間違いなどあるかもしれません。
考慮すべき事項
- 状態の管理に必要な情報
- 情報の受け渡し方法
- 終了条件
状態の管理に必要な情報
今回の問題場合、以下の2つの情報が必要です。
- 人iが既に選ばれたかどうか
- 組に基づくXOR和
人iが選ばれたどうかは、例えば、配列などに記録するか、冪集合で管理します。
情報の受け渡し方法
2パターンに分けてみました。
- 値渡し
- グローバル変数で共有
値渡し
おそらく実装が楽になるので、可能な限りこちらを考慮すべきかなと思います。しかし、情報が配列や二次元配列などを含むと何度もコピーが作られることになるので、この場合は避けた方が無難だと思います。
グローバル変数等で共有
状態に配列が含まれる場合に、必要な変更のみを加えることで計算量を抑えられます。但し、探索が終了した場合に状態を復元する必要があります。
今回の問題では、他の方の実装を見ると、XOR和は値渡しして、人iが使われたかどうかはグローバル変数等で共有しているものが多い様に思います。また、冪集合で管理して値渡しをしているものもありました。
終了条件
探索方法にも絡みますが、終了条件を設定する必要があります。呼び出し先で終了判定するのが良さそうです。
自分の実装では、番号最小の人を選ぶのを呼び出し元で決定してしまっています。組を1つ作って、更に番号最小の人まで選んでしまっているので、終了条件を判定する位置もおかしな場所になり、状態の復元も相まってよくわからない感じになっています。
あとは、図示してみると整理されるのかなと思いますので、時間があれば図示の例を載せたいです。
ABC235F Variety of Digits
自力AC(一度TLEになったので、解法を見て、合っているか確認した)
- 条件を満たす桁に関する問題
- 以下の整数全てに関する問題
という条件から、桁DPが有力候補となります。
桁DPに関する情報はネット上に多くあるため、ここでは割愛しますが、大雑把に説明します。
桁DPでは上位桁より考慮していきます。
の上から桁目をとし、を以下にしたいとします。
の上から桁目までが、と同じだったとします。すると、の桁目をより大きくすると、はを超えます。よって、の桁目は以下にする必要があります。
次に、桁目までで、既にの桁より小さい桁が存在する(すなわち、は未満となっていることが確定している)とします。
この場合、の桁目は0〜9までなんでも良いです。
以上から、の桁目を決定するときに、桁目までがと同じなのか、もしくは既に未満なのか、という情報が必要になることがわかります。
桁DPでは、これを未満フラグとして管理し、
- 未満フラグが0なら、ここまでの桁がと同一
- 未満フラグが1なら、既に未満であることが確定している
とします。
桁DPについては以上です。
最悪となる全状態数と遷移にかかる時間を見積ります。
の各桁に対応して、以下の2つを管理する必要があります。
- 0〜9のどの整数が出現したか
- 未満フラグ
1については、冪集合で管理するので、通りあります。
従って、状態数の最悪は
となります。
最悪計算量については、桁目の全状態に対して、桁目に0〜9を付加することを考えると、状態数の10倍の時間がかかるので、 くらいとなって、なんとか大丈夫です。
実際のDPを考えます。
上から番目の桁までを考えたきに、未満フラグがであって、整数の出現状態がであるものの総和
以下の例で遷移を考えてみます。
例えば、N = 99999として、{1,2}が出現していて、上から3桁目を7にするとします(未満フラグを1で確定している状況)。
この場合、7の付け方は上記の2通りが存在します 。
DP遷移で考えてみます。
ここで問題となるのが、和を計算する時に、700が2個発生しています。なぜ2個発生したかというと、2桁目までで{1,2}が出現したもの(すなわち、という状態であるもの)が2個存在していたからです。
このことから、和を計算するときには、同時に状態の個数が必要となります。よって、以下の2つのDPを考えます。
上から番目の桁までを考えたきに、未満フラグがであって、整数の出現状態がであるものの個数
上から番目の桁までを考えたきに、未満フラグがであって、整数の出現状態がであるものの総和
遷移式は、の桁数を、桁目をとし、桁目をにしたとすると、
となります。但し、はビット毎のOR演算で、は左ビットシフト演算です。
初期値は、で、あとは全て0です。
左辺の詳細はネット上にもありますが、というのは、遷移後に未満フラグが1となるのは、すでに未満フラグが1の時か、または、考慮する桁の数がの桁目未満だった時であるため、このような表現になります(trueを1、falseを0としています)。
なお、このまま実装するとダメな部分があります。
- leading 0を省く処理
実装上、0のみ出現している状態({0,0,0,0,0,0,0,0,0,0,1})が常に1つあることにしておいた方が楽です。しかし、そのままだと0が既に出現していると誤判定してしまいます。そこで、0しか出現していない状態から遷移するときには0が出現していないことにする処理を加えて対処しました。
例
にする。
出現状態は普通にすると
という遷移になってしまうので、0bit目を0にする。
- 10の冪乗部分の処理
を毎回繰り返し二乗法で計算していたら、TLEしました。
そのため、前計算しておき、配列などに保持しておく必要がありました。
答えは、のビットが全て1である集合を部分集合にもつ集合について、の総和です(詳細は実装をご覧ください)。