ARC lessons learned3 - zettsu-t/zettsu-t.github.io GitHub Wiki
ABC 350点問題に対策するため、ARCを解いてみます。
コードはこちら
-1
を出力する。
コードはこちら
要素が1のループつまり
このような降順の組み合わせは、先頭
一般に
公式解説2は同じことをもっと簡潔に述べて、公式解説1は 閉じた式 である。
コードはこちら
最も冗長な並び方は、これまで並んだ列から一つ椅子を空けて、一人または二人組が座る、という方法である。一人組はどこでも座れるが、二人組が座れるかどうかは列の後に空いた椅子が2つ以上並んでいるかどうかで決まる。列の長さ
公式解説も考え方は上記と同じだが、最後の組だけ考えている。
コードはこちら
今回も青diffはダメかと思ったらACした。
出発点はどうあれ、2人の旅人は以下の経路をたどる。そうでない場合は、出発点を後にずらし、終着点(出発点と同じ)も同じだけ後にずらすので、経過時間という観点からは変わらない。
- 2人は地点
$a_A$ にある休憩所で出会う。 - 2人は地点
$a_A$ を互いに別方向に出発し、地点$a_B$ にある休憩所で出会う。$A = B$ かもしれないし、そうではないかもしれない。 - 2人は地点
$a_B$ を互いに別方向に出発し、地点$a_C$ にある休憩所で出会う。$A = C$ かもしれないし$B = C$ かもしれないし、そうではないかもしれない。
地点
地点
std::lower_bound
で見つかるので、計算量は
入力例2に相当する特別な場合として、
基本的に公式解説と同じ解き方である。
コードはこちら
4重ループ
コードはこちら
遅延セグメント木だと思って一時間半溶かした。
遅延セグメント木ではない。行と列は独立した操作なので、行の操作だけ考える。実際に手を動かすと、偶数番目の手順と、奇数番目の手順が異なることが分かる。
0-based indexing で、奇数番目に
- 奇数番目 :
$p..0, (h-1)..(p+1)$ - 偶数番目
-
$q = p$ なら明らかに$0..(h-1)$ -
$q < p$ なら$q..p, (p+1)..(h-1), 0..(q-1)$ -
$q > p$ なら$q..(h-1), 0..p, (p+1)..(q-1)$
-
となるので、偶数番目は
よって奇数番目から偶数番目までの回転回数
コードはこちら
数学
数字の組
なのでそれぞれの桁について
以下のように同じ桁を掛けた部分は
以下のように違う桁だけ注目して、
上の桁から
コードはこちら
有限オートマトン
- 先頭を
$T[1]$ にする。つまり$S[1,L-1]$ に少なくとも一つの$T[1]$ を含む -
$S[1,L-1]$ の各文字を$S_L$ 以降に挟み込める。つまり$T$ の連続しない部分列を$T$ の末尾から取ったときに、$S[L,N]$ に一致する。
前半は累積和で求まる。後半は有限オートマトンで求まる(尺取り法ともいう)。
-
$T$ の文字を指すカーソル$C_t = N$ で初期化する -
$S$ の文字を指すカーソル$C_s = N$ で初期化する -
$T[C_t] = S[C_s]$ なら$C_s$ を一減らす。つまり$S$ の一致部分を一個増やす - 3の結果に関わらず
$C_t$ を一減らす。つまり$T$ の次の文字を見る。 - 3,4を
$C_t = N..1$ まで繰り返す
-1
にする。
公式解説は二分探索で解いており、貪欲法とも書いてあるが、その貪欲法が上記の解答である。
コードはこちら
5時間36分掛かった。
Yes
、そうでなければ No
である。
周期
Yes
、そうでなけば No
である。
Yes
、そうでなけば No
である。
公式解説は、中央ではなく両端から決めることで
コードはこちら
こちらは5時間ではなく30分で解けた。
絶対値を展開して数直線を書くと、
初期値 std::set::lower_bound
で求まる。
公式解説は上記と全く同じである。
コードはこちら
例外処理
典型例はすぐ分かるが、例外を網羅するのが難しい。
まず不変量として、表になっているコインの偶奇がある。なぜならコインを2枚裏返すと、表になっているコインの数は
コインが3枚かつ2枚が表のとき、 110
と 011
が無限ループするのでコインをすべて裏向きにすることはできない。
コインが4枚で 0110
のとき、 0011
, 1010
, 0000
の3回掛かる。これが分からなくて問題を解くのに1時間掛かった。全探索すればわかる。
上記以外ならコインをすべて裏向きにすることができ、基本的には表になっているコインの数の半分回である。ただし1回余分に掛かる場合があり、それは表になっているコインが2枚かつ並んでいるとき、つまり 11...
, ...11...
, ...11
のときである。なぜなら隣接するコインを同時に裏返すことはできないので、一度裏になっている端のコインと 1
を裏返し、表になった端のコインと残りの 1
を裏返すからである。
const std::string zeros{"0000"};
Num visit_dfs(const std::string& current, std::set<std::string>& seen, Num cnt) {
if (current == zeros) {
return cnt;
}
const size_t size = current.size();
Num ans = 100000000;
for(size_t l{0}; l<size; ++l) {
for(size_t r{l+2}; r<size; ++r) {
auto t = current;
t.at(l) = (t.at(l) == '0') ? '1' : '0';
t.at(r) = (t.at(r) == '0') ? '1' : '0';
if (!seen.contains(t)) {
seen.insert(t);
ans = std::min(ans, visit_dfs(t, seen, cnt + 1));
seen.erase(t);
}
}
}
return ans;
}
コードはこちら
組み合わせが大変。
MEXは、数値の集合に穴があれば一つずつ増やせる。つまり元の集合に
-
$i$ 回目およびそれ以降の操作で$H_i$ を埋めない。このとき多重集合は$[0..H_i)$ から$R = K - i - 1$ 回復元抽出する組み合わせなので、$R+H_i-1 \choose H_i$ 通りである。これは$H_i$ 個の要素を$R - 1$ 個の仕切りで区切る方法である。 -
$i$ 回目の操作で$H_i$ を埋める。このとき$i+1$ 回目の操作は$[0..H_{i+1})$ から$R = K - (i+1) - 1$ 回復元抽出する組み合わせである。
これを
${n-1 \choose k} = {n \choose k} \times (n-k) / n$ ${n+1 \choose k} = {n \choose k} \times (n+1) / (n+1-k)$
コードはこちら
ロジックアナライザ
Xを低電圧、Yを高電圧と考えると、XからY、YからXは電圧の遷移に見える。よってXからYへの遷移回数と、YからXの遷移回数の差は、高々1以下でなければならない。つまり No
である。
問題文をよく読むと、入力は0以上である。つまり遷移が全くないことがある。遷移が無いなら低電圧と高電圧の両方を取ることはできない。よって No
である。
それ以外は Yes
である。遷移しないつまり電圧が同じ(XX,YY)ことは、文字列の好きな場所に挟み込むことができる。
コードはこちら
優先度付けに苦しんだ。
YX...XY
と両側を Y
に囲まれた X
が連続 X
を Y
に置き換えると Y
が連続する箇所は何個増えるか考える。 Y
が連続する箇所が増えてお得ということである。
両端つまり X...XY
と片側が開いていて X
が連続 Y
が連続する箇所は X
を Y
に置き換えるなら、 両側を Y
に囲まれた X
を連続長が短い順に埋めて、最後に両端を埋めるのが最適である。
X
の数 Y
にした後に Y
を X
に置き換える。このときの操作は上記とは逆で、元々の Y
を X
に置き換え、その後に両側を X
に囲まれた Y
を連続長が長い順に埋めるのが最適である。
上記の優先度は、 X
, Y
のランレングスに優先度をつけて、
- 両端の場合、
X
なら$- \infty$ ,Y
なら$\infty$ - 両端以外の場合、
X
なら$-len$ ,Y
なら$len$
コードはこちら
不変量
ARCは不変量が頻出で、いい不変量を見つけるとあっさり解けて、そうでないと解けないか実装が大変になるようである。今回は不変量を自力で思いつかなかったので公式解説を読んだ。こんなに短い コード になる。
不変量を使わない冗長な解法なら思いついた。数の差だけが重要なので増分を
- 数の差、
$x_1-x_2$ および$x_2-x_3$ がともに偶数でなければ$x$ を等しくできない。-1
を出力する。 -
$x_1 = x_2 = x_3$ なら答えは0 - 上記でなく、
$x_2 = x_3$ またはなら$x_1$ に4を足して$x_2,x_3$ と等しくする。2回足すと6差が縮まり$x_1 = x_2$ にできる。なので$x_2 - x_1$ が6の倍数でなければ$x$ を等しくできない。6の倍数なら3で割った回数が答えである。 -
$x_1 = x_2$ のときも同様である -
$(x_2 - x_1) < (x_3 - x_2)$ なら$x_1$ に4を、$x_2$ に2を繰り返し足すことで$x_1 = x_2$ にできる。この回数は$(x_2 - x_1)/2$ である。あとは上記と同様に、$x_3$ とそろえる。 -
$(x_2 - x_1) \geq (x_3 - x_2)$ なら$x_2$ に4を、$x_3$ に2を繰り返し足すことで$x_2 = x_3$ にできる。この回数は$(x_3 - x_2)/2$ である。あとは上記と同様、$x_1$ とそろえる。
不変量を使わないと場合分けがとても大変である。
コードはこちら
ほぼ思いついたが6 WAsが取れなかった。
正の値、負の値の集合から、絶対値が大きい順に3つ、絶対値が小さい順に3つ、計24通り取って総当たりすれば解けると思った。のだが、どうしても6 WAsが取れずに諦めた。公式解説を読み、符号を無視して
コードはこちら
ワーシャルフロイド法
幅
ただし頂点自身へは到達不能(距離は0ではなく
ここまでくれば、
コードはこちら
ほぼ解法は見えているのに数時間掛かってしまった。
問題文中の
操作をそのまま実装するとTLEするので高速化を考える。
このような
これを繰り返し、
コードはこちら
1時間と4分
解の方針は立ったが、Fenwick Treeのaddとsumを間違えて時間を溶かした。
-
$A_i > A_j : i < j$ な$j$ が$C_{low}$ 個であることをFenwick Treeで数える。$C_{low} < K$ なら、$A_j$ の$K$ 番目に小さい要素を見つけて$A$ の$[i,j]$ 間を逆順にする -
$A_i < A_j : i < j$ な$j$ が$C_{hi}$ 個であることをFenwick Treeで数える。$M - C_{hi} < K$ なら、$A_j$ の$K$ 番目に小さい要素を見つけて$A$ の$[i,j]$ 間を逆順にする - それ以外なら
$A_i$ を入れ替えないので、$i$ を1足す。併せて、$K$ に$C_{low}$ を足し(少なくともこの順位なので)、$M$ から$C_{low} + C_{hi}$ を引く(順位の上限が下がるので)
この反復が停止したときが答えである。公式解説は
コードはこちら
2日掛かった。夜解いてTLEしたので翌朝解いた。これだけ時間を掛けるとコンテスト中には間に合わない。
-
$x = y = z$ は$x = 1..M$ で成り立つ。順列はそれぞれ1通り。 -
$x = y < z$ は$z = (x+1).. \lfloor N/x \rfloor$ で成り立つ。順列はそれぞれ3通り。 -
$x < y = z$ は$y = (x+1).. M$ で成り立つ。順列はそれぞれ3通り。 -
$x < y < z$ を上手く数える。順列はそれぞれ6通り。
$cumsum(0) = 0$ $cumsum(i+1) = cumsum(i) + N / i - i$
ここで累積和に足す
公式解説2と同じ解法だが、累積和は要らなかった。
コードはこちら
考えるより手を動かす。
同じ数字が何個並んだら、という規則性を見いだそうとしてWAした。
解法2を正答することができず27分掛かってしまった。解法2を実装すると こうなる が、
コードはこちら
変数の使いまわしに注意
入力した値
-
$N<7$ なら解なし(-1) -
$f(N) \leq 3$ なら、上位ビットから順にから3ビット集めて立てる -
$f(N) = 1$ なら最上位ビットしか立っていないので、最上位ビットの一つ下の桁から3ビット連続で立てる -
$f(N) = 2$ なら、最上位ビットではない立っているビットの位置で分かれる-
$N \quad mod \quad 3 > 0$ つまり下位2ビットがいずれも0でなければ、立っているビットより下位ビットに2ビット立てることはできないので、$f(N) = 1$ と同じにする。 - そうでなければ最上位ビット以外で立っているビットの、一つ下の桁から2ビット連続で立てる。最上位ビットも立てる。
-
あとは立てたビット位置3つから答えを求める( ビット
コードはこちら
D問題なのに25分で解けた。
完全グラフの変数は
それ以外の場合は解がある。まず完全グラフつまり
完全グラフ以外の場合、最初に正
以上から解が求まった。公式解説と答えは同じだが、公式解説の証明はハミルトン閉路を用いている。
コードはこちら
線を描く
往路の順位と往復の順位を描くと、線が交わったり交わらなかったりする。交わらないというのは誰にも抜かれなかったことを意味するので、区間賞になりうる。つまり自分より遅く出発して、自分より早く着いた選手以外の数が答えである。
図を描いて、誰にも抜かれなかった(抜いたかもしれないし抜かなかったかもしれない)ときは垂直な縦線、抜かれたときは垂直線を左上から右下に横切る斜線を描くと分かりやすい。鉄道ダイヤグラムの急行と普通の関係である。公式解説通りに
コードはこちら
不変量
解説を見ないと解の方針が立たなかった。1から順番に数を左から定位置を決めていく、末尾にあるときは二つ左に引っ張り出す、を繰り返せば解ける。不変量が重要で、解があるなら
実装方法は std::rotate
が一番簡単である。定位置 std::find
で都度見つけてよい( std::rotate
が律速なので)。
ここまで考えなくても上記の通り実装して、 No
を出力すればよい。
コードはこちら
二分割だけ考える
単に分割する場所を総当たりすればよかった。難しく考えすぎてデバッグに苦しみ、30分以上掛けてしまった。狭義単調増加は std::string::operator<
である。
コードはこちら
穴をボールに寄せる。
-
$A_1$ を$A_i$ にしたときの増減回数は$min(0,A_1-A_i)$ である。$A_i \leq A_1$ なら$A_i$ を減らして$A_1 \leq A_i \leq A_2$ の範囲を拡大する。$A_i > A_1$ なら既に$A_1 \leq A_i \leq A_2$ の範囲に居るので$A_1$ を増減する必要はない。 -
$A_2$ を$A_{i+M-1}$ にしたときの増減回数は$min(0,A_{i+M-1}-A_2)$ である。理由は同様。 - よって
$i=3..N$ を固定したとき、総増減回数は$min(0,A_1-A_i)+min(0,A_{i+M-1}-A_2)$
コードはこちら
3進数
冗長でない3進数、つまり各桁が0,1,2のいずれかである3進数で
3進数を冗長にする、つまり各桁が3以上になることを認める。このとき冗長にすると、
コードはこちら
ループ検出
ループを一周して、今いる頂点と異なる色に移動するのを繰り返すことができればよい。これはある出発点から、頂点と異なる色に移動するのを繰り返し、出発点の頂点と一周直前(出発点の隣)の頂点が同じ色ならよい(出発点の頂点の色は反転しているので)。
問題は、上記の条件を満たすループ検出を効率よく実装しないとTLEすることである。以下のようにDFSするとTLEしない。
- 出発点は頂点
$1..N$ をすべて試す。辺の数が少ないので問題ない。 - 最初の移動では、今いる頂点と同じ色に移動する。そうでないなら次の候補となる頂点を探す。
- 二番目以降の移動は、今いる頂点と異なる色に移動する。そうでないなら次の候補となる頂点を探す。
- 直前の頂点には戻らない(同じ色なので戻ることができない)。訪れたことの無い頂点を再帰的にたどる。
- ループを検出する、つまり一度訪れた点をもう一度訪れた場合、それが出発点なら上記の条件を満たす(DFSはtrueを返す)。そうでなければDFSはfalseを返す。
- DFSがtrueを返したらそれ以降の探索を打ち切って
Yes
を出力する。trueを返すケースがなければNo
を出力する。
公式解説通り、union-find木を使うと簡単に 実装 できる。
コードはこちら
DPではなかった。
DPだと思ったがそうではなかった。Aliceにとっての最適反応は、裏返すとBobが最大損するカードを裏返すことであり、Bobにとっての最適反応は裏返されると最大損するカードを真っ先に取ることである(今取らなくても後で再度裏返しになって取れるようになるかもしれないが、今取っても損はしない)。
つまり現時点で 表の数字-裏の数字
が最大のカードをAliceはひっくり返し、Bobは取るのが最適反応である。
{表の数字-裏の数字, 表の数字, 裏の数字}
を優先度キューに載せることで管理できる。
- Aliceは優先度キューの先頭、つまり表の数字-裏の数字が最大のカードを取って、裏返して優先度キューに載せる。
{裏の数字-表の数字, 裏の数字, 表の数字}
を載せればよい。 - Bobは優先度キューの先頭のカードを取り除き、表の数字を得点として得る。
公式解説には
コードはこちら
13分掛かる。
灰色とは思えない難易度である。 No
、そうでなければ Yes
と予想できる。
No
である。
そうでなければ Yes
である。
コードはこちら
3日間かかった。なんとなく方針は立ったが、エッジケースを詰めるのに時間が掛かった。
ソートをちょうど1回するので、ソートすると
ある
このような
理由を考える。
公式解説は、論理的に探索区間を限定している。
コードはこちら
重実装すぎて解けなかった。
C
な場所は C
でなければならない(操作で C
は作れないので)。 C
で区切られた区間(終端に C
が暗黙にあるとみなす)において、操作3で A
を右に移動できる。ここまでは分かったが、 C
から A
, B
をどう作るかが分からなくなった。
公式解説にある通り、 A
はできるだけ左に、 B
はできるだけ右に寄せて、 A
を A
に移動できるか調べればいい。こう書くと簡単に見えるが実装が結構重い。順番に処理を書く。
-
$Y$ のC
で区切ったランを作る。入力の$X,Y$ にC
を追加しておくと、終端処理が楽になる。 -
$X,Y$ を先頭から走査して、$i$ 文字目つまり$Y[i]$ がC
以外なら、$X,Y$ の連続部分文字列の末尾に追加する -
$Y[i]$ がC
なら、$X[i]$ がC
以外なら答えはNo
である。そうでなけば以下の処理を行う
-
$X$ の連続部分文字列に出現するA,B,C
の個数を$X_a, X_b, X_c$ とする -
$Y$ の連続部分文字列に出現するA,B
の個数を$Y_a, Y_b$ とする -
$X$ のC
をA
に転換して、$Y$ と同じ個数にする。A
が足りないつまり$X_a < Y_a$ のままなら答えはNo
である。 -
$X$ のC
をB
に転換する -
$X,Y$ のB
の個数が一致しなければ答えはNo
である。 -
$X,Y$ の先頭から$i$ 番目のB
をそれぞれ、$PX_i, PY_i$ とする。$PX_i < PY_i$ となる$i$ があったら答えはNo
である。 - 上記以外なら答えは
Yes
である
コードはこちら
ある数
std::pair<Num,Num>
をソートすればよい)。
基本的に
このようなすべての組み合わせについて、最小の操作回数が答えである。この方法は公式解説の解法2と同じである。
コードはこちら
最小二乗法
美味しさの大きいトースト
公式解説は、0をパディングすることで証明している。実装は こちら
コードはこちら
任意精度整数で通る。
除算切り捨てをmodintでする上手い方法が無いので、任意精度整数( boost::multiprecision::cpp_int
)で解いた。最後に 998244353LL
で割ってから long long int
にする(int
にしてから割ると間違える)。
よって
全ての
コードはこちら
転倒数になるのは連続する短調減少の中だけである。なぜなら短調減少が止まったら、その次の単調増加を十分高い値に増やすことで、それ以前の数より大きくできるからだ。公式解説は具体的な値を使っていて、考え方は同じである。
連続する短調減少が長さ
コードはこちら
-
$A_i$ が空なら0
-
$\oplus A \ne 0$ なら-1
しかし肝心なGrundy数を上手く定義できなかった。Grundy数の定義を公式解説通りに行うとACする。
コードはこちら
湧き出しを考える。
頂点1つまり根からもっと遠い頂点から無限に湧き出した値が、他の頂点に波及する。そこで +
、最初に負の値を見つけたら -
、正負どちらも見つからなければ 0
を出力する。
考え方としては、最も
コードはこちら
境界値がややこしい。
-
$L = 1$ なら、左端から$A_R$ まで足した和が$S$ 以下である。このとき、$f([i,R])=1 : i=1..R$ である。$f(x)$ の累積和を$g(x) = \sum_i f(i)$ として、$g(x) = x : x=1..R$ である。 -
$L > 1$ なら$f([i,R])=1 : i=L..R$ である。この部分について$g(R)=R-L+1$ である。さらに$i < L$ について、最後の一区間を加える対象が$1..(L-1)$ の$L-1$ 通りで、加えられる$f()$ の総和が$g(L-1)$ なので、$R + g(L-1)$ である。
答えは
コードはこちら
B
を A
にしたい場所)と、 A
を B
にしたい場所)をそれぞれ記録する。
ここから尺取り法で求める。B
を A
にしたい場所( A
を B
にしたい場所(
-
$i$ が尽きたら、最左の$i=left$ について、$k < left$ なる$k$ について$S_k$ をA
からA
に置き換える。そのような$k$ が十分たくさんなければ置き換えられないので-1
を出力する。そうでなければ残りの$j$ をすべてA
からB
に置き換える。 -
$j$ が尽きたら、最右の$j=right$ について、$right < k$ なる$k$ について$S_k$ をB
からB
に置き換える。そのような$k$ が十分たくさんなければ置き換えられないので-1
を出力する。そうでなければ残りの$i$ をすべてB
からA
に置き換える。 -
$i < j$ なら、$S_i$ をA
で、$S_j$ をB
で置き換える操作をまとめて行い、次の$i,j$ に移る。 -
$i > j$ なら、$S_i$ をA
に置き換え、$i < k$ なる$k$ があればB
をB
に置き換え、次の$i$ に移る。そのような$k$ がなければ置き換えられないので-1
を出力する。
コードはこちら
遅延セグメント木
左右の幅は広く取りすぎても問題ない。つまり
1..10
から総当たりする。すべての 1..10
の出現位置を求めて置けば二分探索で求まる。
公式解説2の方法に似ている。
コードはこちら
仮にルークを対角線上に置く。この配置はルーク同士が攻撃しあわないことを満たす。 No
を出力する。
次にポーンを置くことを考える。横方向(列方向には)
行を入れ替えて、可能な限り偶数行にポーンが無いようにすることを考える。このときポーンが無い行は No
を、そうでなければ Yes
を出力する。
コードはこちら
入力例から察した。
解法が分からないので、入力例3を全探索した。以下の18通りの
2 6 7 1 3 4 8 5
2 6 7 1 3 5 8 4
2 6 7 1 4 3 8 5
2 6 7 1 4 5 8 3
2 6 7 1 5 3 8 4
2 6 7 1 5 4 8 3
2 6 7 3 1 4 8 5
2 6 7 3 1 5 8 4
2 6 7 3 4 1 8 5
2 6 7 3 4 5 8 1
2 6 7 3 5 1 8 4
2 6 7 3 5 4 8 1
2 6 7 4 1 3 8 5
2 6 7 4 1 5 8 3
2 6 7 4 3 1 8 5
2 6 7 4 3 5 8 1
2 6 7 4 5 1 8 3
2 6 7 4 5 3 8 1
何となく規則性が予想できる。 666
, 888
のそれぞれ先頭2数が固定である。
-
$i=1..N$ について、$i = A_j$ となるような$A_j$ の集合$G_i$ を求める -
$max(G_i) \ne i$ なら解なし(0通り)である。このとき$B_i = i > A_i$ になるからである。 -
$max(G_i) = i$ なら-
$|G_i| = 1$ なら特に対処しない -
$|G_i| = S > 1$ なら、$G_i$ の末尾以外をシフトして$P$ に設定する。$G_i= [G_{i,1}, ..., G_{i,S}]$ として$P_{G_{i,j}} = G_{i,j+1} : j < S$ に固定する。
-
解ありのときに、固定していない番号に何通り選べるか求める。
- 初期値
$C_0=1$ とする -
$i$ について$P_i$ の選び方を$C_i$ 通りとする。ここで-
$P_i$ が固定されていれば1通り -
$P_i$ が固定されていなければ$C_i = i - seen(i) - cnt(i)$ -
$seen(i)$ は$P_1..i$ のうち、すでに固定された要素数。セグメント木に載せて$P_i$ を1増やす。 -
$cnt(i)$ は$P_1..i$ のうち、固定されていない要素数。$P_i$ を固定していなければ1増やす。
-
- こうして求めた
$\prod_{i=0}^N C_i$ が答えである。
上記はサイクル分解であり、公式解説もそのような方針である。公式解説は題意を上手くグラフの性質に言い換えている。
コードはこちら
Greedyで解ける。
チョコレートの長方形片の縦横の長さを
これを繰り返して、すべての Yes
, そうでなければ No
である。
$(0,0) \quad if \quad PY = L \land PX = L$ $(L,PX-L) \quad if \quad PY = L \land PX > L$ $(PY-L,L), (PY,PX-L) \quad otherwise$
公式解説は、
コードはこちら
条件を満たさない文字列とは、
よって文字の選び方は以下の通りである。
-
$s$ の 1文字目は$L$ 通り -
$s$ の 2文字目は$L-1$ 通り -
$s$ の 3文字目は$max(L-min(2,W))$ 通り - より一般的には、
$s$ の$i$ 文字目は$max(L-min(i-1,W))$ 通り
これを掛けたものが答えである。
コードはこちら
添え字を一個間違えて諦めた。
不正解は こちら で、最後のループが一個足りない(2から始めているが正しくは1である)。ここさえ直せば正解できたが、解法に自信が無いと気が付かない。
公式解説にある通り、投票者1を挿入するのを全部試せばいい。別解として上記の通り、変化点を数えてもよい。
コードはこちら
桁DP
最上位桁が非0 (leading zeroを除去した)な数
-
0..9
の任意の数を選ぶと$Y$ を超える可能性があるなら$i = 1$ , そうでなければ$i = 0$ - 今注目している桁が
$j = 0..9$
とする。
まず最上位桁に設定できる数字
-
$P_1 < D_1$ なら、$dp[0][P_1] = 1$ 。つまり下位桁を任意に 0..9 を組み合わせても$Y$ を超えない。 -
$P_1 = D_1$ なら、$dp[1][P_1] = 1$ 。つまり下位桁上手く選ばないと$Y$ を超えてしまう。 - 上記以外は0
である。次に最上位桁以外の上から
-
$P_i < D_i$ なら、$next[0][P_i] = dp[0][P_{i-1}] + dp[1][P_{i-1}]$ 。つまり$Y$ を超えそうだったものも、今後は超えなくなる。 -
$P_i = D_i$ なら、$next[0][P_i] = dp[0][P_{i-1}], next[1][P_i] = dp[1][P_{i-1}]$ 。つまり$Y$ を超えないものは超えないままだし、超えそうなものは超えそうなままである。 -
$P_i > D_i$ なら、$next[0][P_i] = dp[0][P_{i-1}]$ 。つまり$Y$ を超えないものは超えないままだし、超えそうなものは選べない。
である。この条件下で、 0..9
の組み合わせ100通りを網羅して
最上位桁を非0としたので、
ここまで来たので、
公式解説は、次の桁が9通りか0通りかどちらかしかないことを使ってDPを簡略化している。
コードはこちら
解説を読むまで全く分からなかった。
直線
-
$X$ が変わらないつまり垂直、$Y$ 軸に平行なら$(SX=X, SY=0, DX=0, DY=1)$ -
$Y$ が変わらないつまり水平、$X$ 軸に平行なら$(SX=0, SY=Y, DX=1, DY=0)$ - それ以外は
$(SX=DX, SY=Y \times DX - X \times DY, DX=DX, DY=DY)$ 。ただし以下の変換を行った後である。- 傾き
$(DX,DY)$ は$gcd(DX,DY)$ で割って、$X$ が負なら$(DX,DY)$ の符号を反転する -
$Y$ 軸の切片は$Y - X \times DY / DX$ なので、分母を掛けて整数にし、分母を$SX$ にする。
- 傾き
コードはこちら
茶diffに一時間半
左右から要らない要素を削って残った連続列を
-
$C = 1$ なら、$S$ が答えである -
$C > 1$ なら、$l$ が固定なら$max [l,r] for r \in (l+1)..N$ となる$r$ を求めて、$A_l..A_r$ を$C-1$ 倍したものを$S$ に加える。すべての$l$ についての最大値が答えである。 -
$C < 1$ なら、$l$ が固定なら$min [l,r] for r \in (l+1)..N$ となる$r$ を求めて、$A_l..A_r$ を$C-1$ 倍したものを$S$ に加える。すべての$l$ についての最大値が答えである。
累積和をセグメント木に載せることで、区間の最大最小値が分かる。
コードはこちら
上記の緑とは打って変わって31分で解けた。
私の場合、ARCは解答時間のばらつきが大きい。解の方針は立ったが、場合分けを整理するのに時間が掛かってしまった。
買うレビューは星4,5しか意味が無い。星1,2,3を買っても平均評価を星3未満から星3以上に上げることはできないからだ。
これを式変形すると、
-
$V < 0$ ならそもそも星を買う必要はないので答えは0である -
$B_4 > B_5$ なら星4を買う意味はなく、星5だけ買えばいい。答えは$\lceil V/ 2 \rceil B_5$ である。 -
$2 \times B_4 > B_5 \geq B_4$ なら星5で2価値を買い、1価値が端数なら星4を買う。答えは$\lfloor V / 2 \rfloor B_5 + (V mod2) B_4$ である。 - 上記以外、
$B_5 \geq 2 \times B_4$ なら星5を買う意味はなく、星4だけ買えばいい。答えは$V B_4$ である。
コードはこちら
実験したら21分で解けてしまった。A,B問題より速い。
解析解に見当がつかないので候補を列挙したら、 1, 80, 90-99, 100-109, 9800, 9900-10099, 998000, 999000-1000999, ...
が見つかる。よって解 1
以外については、
公式解説には論証があるが、実験結果をそのまま実装するとACできることが前提だった。
コードはこちら
全く解ける気がしない。
最終形態を想定すれば解けることが分からなかった。出力例をみると、立っているビットは高々2つなので、2の階乗を2個足したものと気づけば解けたかもしれない。
最終結果は全員左側を取るか、全員右側を取るかどちらかしかない。どちらか決め打ちしてシミュレーションする。
- 決め打ちした左右のスプーンをすでに取られていたら、0通りである
-
?
のとき、決め打ちした左右のスプーンの反対側をすでに取られていたらLR
を問わないので2通り、そうでなければ1通りである。 -
LR
は問題文通りに取って1通りである。その結果、後の人はスプーンが取れないかもしれない。
コードはこちら
入れ替え操作で、 (
と )
の数を変えることはできない。よって置き換え操作が要る。 (
が )
が )
を (
に置き換える。このとき最左の )
から順に (
に置き換える。なぜなら (
が左にあるほど正しい括弧列を作りやすいからだ。同様に (
を右からから順に )
に置き換える。
後は (
と )
の不整合があれば置き換える。置き換え一回のコストは )
の位置と、 )
との対応が取れていない (
の位置を記録する。その後、最左の正しくない )
の位置と、最右にある )
との対応を順に取っていく。
コードはこちら
諦めた。以下0-based indexingで説明し、
公式解説を読んだら、別に入れ替える必要なかった。
コードはこちら
コードはこちら
久しぶりのARC 灰diffである。
大は小を兼ねる。つまり500円硬貨は100円硬貨を兼ね、100円硬貨は50円硬貨を、50円硬貨は10円硬貨を、10円硬貨は5円硬貨を、5円硬貨は1円硬貨を兼ねる。よって大きな金額から順に使い、足りなくなったら小さな金額で払うようにすればよい。こうすれば大きな金額でお釣りが必要な場面が、小さな金額でお釣りが要らない場面より後に来ることは無くなる。
後は買い物の順番について順列組み合わせを全部試せばよい。と思ったら、順列は関係ないことが公式解説で分かった。
コードはこちら
ものすごく久しぶりのARC B問題 灰diffである。入力例の妥当性を確かめずに10分以内に提出してACした。
1-based indexingで、最右にある1が
この要領で、最右にある1から順に0から1に変えればよい。操作回数は高々
コードはこちら
緑diffが解けた。
左上から右下への経路と、左下から右上への経路は、少なくとも1マスを共有する。なぜならマスを共有しない経路は構築不可能だからだ。
そこで四隅からの他のマスへの距離を01-BFSで求める。ここで距離とは、始点とは色が違うマスの数である。始点は制約から距離0とし、終点のマスの色は 数えない 。その手前までの距離を記録する(うっかり実装を間違えたのだが実は正解だった)。
こうすると、それぞれのマスから四隅への距離の和 + 1が、あるマスを共有したときに紫色に変えなければならないマスの最小数である。+ 1したのは共有するマスは必ず紫にしなければならないからだ。あとは四隅を含めてすべてのマスについてこれらの値を求め、最小値が答えである。
公式解説を見たら、2経路で共有するマス云々を考える必要はない、なぜなら赤から紫に変えるのと青から紫に変えるのは独立な事象なので、と分かった。これをとっさに思いつくことができない。
コードはこちら
題意を読み解くのがかなり大変である。
-1
を出力する。ここから
3
を挟んで
4
を挟めばよい。辞書順で最小なので 4
を3番目に置けばよい。
ということは -1
を出力する。こうしてできた
公式解説の一番下にある方法と同じである。よく見たら、
コードはこちら
ARC 水diffには珍しく(?)、鋭い発想もエッジケースの考慮も必要なくて丁寧な数え上げで解ける。
補集合の考え方も含めて、公式解説1とだいたい同じである。上記を
コードはこちら
38分と3ペナである。
累積和の先頭が0というのが壁になる。まず昇順に並べてみる。このとき累積和は
- 先頭が0
- その後
$X$ に負の値があるなら累積和が負になる - その後累積和は正になるかもしれない
である。
どちらでもなければ解なしなので No
を出力する。
コードはこちら
解法はすぐ思いついたが、詰めに時間が掛かった。
以下
DPの状態遷移を定める。
コードはこちら
解法は早めに思いついたが、昇順の
sum(purrr::map_dbl(2:1000, ~ ceiling(log2(.x))))
# 8977
std::vector
を用いたとしても、 std::vector
に添え字で insert/erase して要素数に比例する計算時間が掛かってもTLEしない(96 ms)。
-
$A_1 < A_2$ かどうか比較する。 -
$i > 2$ について、$A_{P_j} \leq A_i \leq A_{P_{j+1}}$ を満たすような$j$ を二分探索で求める。$A_i \leq A_{P_{1}}$ または$A_{P_{N}} < A_i$ の場合も考慮する。この実装に時間が掛かってしまった。
-
$-R \leq b \leq 0 \leq c \leq R$ なら$-R \leq b+c \leq R$ である -
$0 \leq b \leq c \leq R$ なら$\sum_{i=1}^N A_i \leq R$ より、$0 \leq b+c \leq R$ である -
$-R \leq b \leq c \leq 0$ なら同様に$-R \leq b+c \leq 0$ である
つまり
想定解法はマージソートだった。 std::vector
に添え字で insert/erase すると要素数に比例する計算時間が掛かることを承知で、C++の力でごり押した感がある。
コードはこちら
B問題以降が難しすぎて、これがratedなら1完でもレーティングが上がった可能性がある。
ABA
が A
、 BAB
が B
、ということは、文字列に A
と B
の入れ替わりが2つ連続すればまとめて削除できる。よって連続する文字の入れ替わりが
公式解説も方針は同じだが、使っているテクニックは驚きである。
コードはこちら
全く見当がつかなかった。
コードはこちら
丁寧に場合分けすればいいが、途中で実装が分からなくなって47分掛かった。
-
$P = (1,...,N)$ なら答えは0 -
$P = (1,...)$ または$P = (...,N)$ なら端以外をソートして答えは1 -
$P = (N,...,1)$ なら、$P[1,N-1]$ をソートして左端の$N$ を押し出す、$P[2,N]$ をソートして右端の$1$ を押し出して$N$ を右端に収める、$P[1,N-1]$ をソートして答えは3 - 上記以外で
$P = (N,...)$ または$P = (...,1)$ なら、$P[1,N-1]$ をソートして左端の$N$ を押し出す、$P[2,N]$ をソートして右端の$1$ を押し出して$N$ を右端に収めると答えは2
上記以外は、ある添え字
公式解説によれば、分割可能かどうかは累積min,maxで求まる。分割可能でなく、
コードはこちら
TLE解しか思いつかなかった。
自明な場合は分かるが、そうでないときに
-
$X_0 = Y_0 \land X_1 = Y_1$ なら$S=T$ とすれば解の条件を満たすのでYes
- そうではなく、
$X_0 \neq Y_0 \land X_1 = Y_1$ ならどんな$T$ を選んでも$S$ の繰り返しを一致させることができないのでNo
- そうではなく、
$(X_0 - Y_0)(X_1 - Y_1) > 0$ なら、$S$ (空ではない)と$T$ をどう選んでも、$X$ または$Y$ について$S$ または$T$ の繰り返しが余分なのでNo
上記以外では No
である(公式解説にある通り上記3を No
と言い換えてもいい)。ここから先が分からなかった。
コードはこちら
何時間考えても分からず諦めた。
解き方は公式解説にある通りである。DPで解こうとして結局解けなかった。出力例3が2の60乗と分かったのを大事にすべきだった。
制約から解き方を察する。クエリ
- すべてのクエリについて、左右どちらにもできると初期化する
-
$V_i \leq V_j$ なら、クエリ$Q_i$ はどうしてもよい -
$P_i = P_j$ なら、クエリ$Q_i$ を左右どちらにしても$Q_j$ と衝突が避けられないので、答えは0である -
$P_i < P_j$ なら、クエリ$Q_i$ を左、$Q_j$ を右に固定する。逆に言うと、$Q_i$ を右に置けない、$Q_j$ を左に置けないと設定する。これらは上書きして構わない(制約が厳しくなる方にしか行かないので)。 -
$P_i > P_j$ なら、$Q_i$ を左に置けない、$Q_j$ を右に置けないと設定する
全クエリを処理して、取りうる組み合わせ(左右どちらでもなら2、左右どちらか一方なら1、左右どちらも不可なら0)の積が答えである。
コードはこちら
A問題より易しい。ペナルティの山を築きつつ52分で解いた。問題選びがとても重要である。
制約から
0,1,00,01,10,11,000,001,010,011,1000,...
という順番で残す。バイナリツリーをBFSして根から各頂点までのパスを列挙する、という雰囲気である。具体的には、キューを使って
- キューの初期メンバは組
$(2^{K-1}, K-2)$ とする。これは$2^{K-1}$ のビット$K-2$ (LSBを0と数える)を操作するという意味である。併せて、$A = { 2^{K-1} }$ とする。 - キューから
$(V,P)$ を取り出したら、$A$ に$V + 2^{P}$ を追加する。要するにビットを立てた値を$A$ の新たなメンバとする。その後$(V,P-1)$ と$(V + 2^{P}, P-1)$ をキューに入れる。 - これを
$|A| = N$ になるまで繰り返す。$2^{K-1} < N$ なのでこの操作はいつか止まる。
上記は公式解説2の上のbitから構成する方法である。ビット反転してもよかった。
コードはこちら
桁DPかと思ったら入力例通りだった。
コードはこちら
やはり600点問題は厳しく、翌日2時間38分掛けて解いた。
硬貨
- 異種なら一方が本物、一方が偽物なので、後で判定するよう
$i$ を$D$ に保存しておく。 - 同種なら両方とも本物もしくは両方とも偽物なので、union-find木でマージして代表元を
$L$ に保存しておく。このときunion-find木における$size(x)$ を、$x$ が属する集合の大きさとする。
-
$l = r$ なら、その要素を後で比較することにして、$S$ に$L_l$ を追加して比較を終了する。 -
$Z = M - |D| - |U| < size(x)$ なら、$x$ を代表元とする集合はすべて本物の硬貨である。よって$size(L_1) > Z \land size(L_r) > Z$ なら、両方とも本物なので、クエリを投げることなく$L_1$ と$L_r$ をマージして$S$ に追加する。 - そうでなければクエリを投げる
- 同種なら
$L_1$ と$L_r$ をマージして$S$ に追加する。 - 異種かつ
$size(L_l) > Z$ なら、$L_r$ を代表元とする集合はすべて本物の硬貨でなので、$U$ に追加する - 異種かつ
$size(L_r) > Z$ なら、$L_l$ を代表元とする集合はすべて本物の硬貨でなので、$U$ に追加する - 上記以外なら本物か偽物かはまだ分からないので、
$L_l, L_r$ を$S$ に追加する。
- 同種なら
一通り
こうすると偽物の硬貨は
上記の解き方は公式解説2-1に似ている。公式解説1の通り解くと こう なる。この発想が無いと解答時間が2時間に収まらない。