−目次
AtCoder Regular Contest 173 A,B,C,D問題メモ
ケアレスな実装ミスでタイムロスしちまうのはなおらないねえ。
A - Neq Number
問題
- ある正整数 X を(先頭に0のつかない)十進法表記した際に、同じ数字が隣り合わない場合、X を“Neq Number” という
- 小さい方から K 番目の Neq Number を求めよ
- 1つの入力につき T ケース与えられる
- 1≤T≤100
- 1≤K≤1012
解法
「d 桁の Neq Number は何個あるか?」は、簡単に求められる。
- 一番大きい桁は 1~9 の9通り
- 続く d−1 個の桁は 0~9 のうち前の桁と違う数字の9通り
- → 全部で 9d 個ある
なので、まず「d 桁以下のNeq Numberの個数」を事前計算しておくと、K 番目のNeqNumberが何桁かは判明する。
同様に、大きい方から何個かの先頭桁を決めたときに「これに当てはまる Neq Number は何個あるか?」も求められる。
各桁、前の桁の数と違う数の選択肢があるので、9未定桁数 ある。
1 2 3 4 ? ? ? ? それぞれの"?"に適当な数を置いてできるNeqNumberは、9^4=6561個ある
i 桁目に置く数を順に試す。答えが 123?????
を満たす中で 20000 番目である場合、
i 1 2 3 0 ? ? ? ? 1~ 6561 1 2 3 1 ? ? ? ? 6562~13122 1 2 3 2 ? ? ? ? 13123~19683 1 2 3 3 ? ? ? ? ←×違反 1 2 3 4 ? ? ? ? 19684~26244 → 答えは、1234???? を満たす中で 20000 - 19683 = 317番目
なので、大きい方の桁から 0,1,2,...,9 のうち前の桁と異なるものを置いていき、個数を数えていくと、順に決めていける。
1つにつき、進数を B=10、Kmax 番目のNeqNumberの桁数を D として(D=14~15 くらい)、O(BD) で求められる。
B - Make Many Triangles
問題
- 2次元空間上に N 個の点がある
- これらの点を頂点に持つ(非退化な)三角形をできるだけ多く作りたい
- 1度使った点は他の三角形では使えないとき、最大何個作れるか、求めよ
- 3≤N≤300
解法
基本的には、⌊N3⌋ 個作れるはず。
しかし同一直線上にある3点は、三角形が作れないので選ぶことができない。(作れないのはこの条件のみ)
点同士を結ぶ直線のうち、最も多くの点が並んでいる直線を L、そこに乗っている点の数を C とする。
L 上の点をなるべく多く消費することを考えると、L 上から2点と、それ以外から1点を選ぶことで三角形が作れる。
よって、そうやって作っていっても L 上に点が残る場合、作れる個数は N−C 個となる。
答えは、min となる。
同一直線上に並ぶ点のカウント
「同一直線上に並ぶ点の個数」を求めるには、2点組ごとに「2点を結ぶ直線の方程式 ax+by+c=0 の係数 a,b,c」を計算し、それごとにカウントする。
c 個の点が並ぶ直線は、\dfrac{c(c-1)}{2} 回カウントされることになるので、
最も多くカウントされた直線の値を逆算すると個数が分かる。
(x_1,y_1) と (x_2,y_2) を通る直線の方程式を、「小数による誤差を排除して」「x 軸や y 軸に平行な場合にも対応して」求めるには、以下の式を使うのがよい。
- d_x=x_2-x_1,d_y=y_2-y_1 として、d_yx - d_xy + (d_xy_1 - d_yx_1)=0
ただし、係数に定数倍をかけたものは同一になるので、それらを統一する必要がある。
- d_x と d_y は、GCDで割って互いに素にしておく
- d_x が負の場合、正にする
- d_x が0かつ d_y が負の場合、d_y を正にする
こうすると、定数倍の違いを吸収して、3つの整数値で直線を特定できる。
C - Not Median
問題
- 1~N の順列 A=(A_1,...,A_N)
- 各 i=1,...,N につき、以下を満たす区間 [l,r] があるか判定し、存在する場合は長さ r-l+1 の最小値を求めよ
- 区間長は奇数
- i を含む(l \le i \le r)
- (A_l,A_{l+1},...,A_r) の中央値は、A_i ではない
- 1 \le N \le 3 \times 10^5
解法
最小の区間は3である。特に、i の左右が、A_i より小さい同士/大きい同士で一致していれば、確実に3となる。
i 両隣が Ai より大きい同士なら、 A 8 5 6 [i-1, i+1] の区間にすれば確実に最小の3になる
それより長くなる場合というのは?
A_i 自身より大きい数と小さい数が左右に交互に現れ、 1つずらしても「小さい数が出て小さい数が入る」「大きい数が出て大きい数が入る」ようにしかならない場合に、長くなる。
i j A 7 14 6 8 10 2 9 5 4 ... ^:8より大きい v ^ v ^ v ^ v v v:8より小さい ~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~ i を含む長さ5の区間は、 ~~~~~~~~~~~~~~ 全て8が中央値になる
上例 5 4
のように大きい数同士、小さい数同士が隣り合った箇所(j)を含めた区間にすると、中央値を変えられる。
(※端の場合を除く。後述)
このように、A_i より大きい同士/小さい同士が隣り合う箇所を単に「連続箇所」とする。
各 i につき、左右で最も近い連続箇所のindexを高速に求めたい。
区間に連続箇所を含められればよいので、もう一方の端は、i をギリギリ含めるか、もしくは1つだけ逆側を含めるかになる。
左右で近い方の連続箇所との距離を、奇数に調整したものが、答えとなる。
i v連続箇所 A 7 14 6 8 10 2 9 5 4 ... v ^ v ^ v ^ v v ~~~~~~~~~~~~~~~~~~~~ 連続箇所から i を挟んで逆側(6)を、1つだけ含めて調整した場合
端の場合
端の場合、「逆側を1つだけ含める」ができない。
左端なら、区間を2個ずつ右に伸ばすしかない。
i A 8 10 2 9 5 4 12 ... ^ v ^ v v ^
その場合、小さい同士が連続(5 4
)していても、次が大きい(12
)なら、やはり中央値は8である。
左端・右端は、愚直に2個ずつ伸ばして確かめていくのがよい。
※端で無い場合は、「A_i の左右の大小は異なる」前提から、この心配は無い。
例えば i を左端、右側の連続箇所を右端とした区間が
- 長さが奇数なら、それでよし
- 長さが偶数なら、もう1要素、左か右に追加しなければならないが、、、
- 「小 i 大 小 大 小 小」のように、偶奇を考えると逆側の1個と、連続する値の大小は必ず一致する
最も近い連続箇所の求め方
複数の求め方がある。
- セグメント木
- SortedSet
- 実は愚直で間に合う
セグメント木
A_i の小さい方から順に答えを求めていく。
現在、答えを求めている A_i を k とする。
B=(B_1,...,B_{N-1}) を、「A_i と A_{i+1} が、ともに k より小さい、またはともに k より大きい場合に1、それ以外は0」を取る値とする。
A 7 14 6 8 10 2 9 5 4 12 11 3 13 1 B 1 1 1 1 1 1 1 1 1 1 1 1 1 k=1 のとき B 0 0 0 1 0 0 0 1 0 1 0 0 0 k=8 のとき
B を区間Maxセグメント木に載せて、その上で二分探索することによって、 「[l,r) の範囲のうち最も左にある1」「最も右にある1」の位置を取得できる。
これで、左右に最も近い連続箇所の位置が求められる。
k=A_4=8 の答えを求め終わったら、次の k に向けて、A_3,A_4,A_5 の大小を調べて B_3,B_4 を更新する。
愚直
i から左/右に1つずつ、連続箇所が出てくるまで調べる実装でも、間に合う。
ある i で時間がかかる場合というのは、左右に A_i より大/小が交互に出現する場合である。
このとき、通過した各要素は、その要素にとっては「両端が大きい同士/小さい同士」に該当するので、答えは3とすぐに求まる。
i x をつけた要素は、 A 7 14 6 8 10 2 9 5 4 ... 両端が大きい同士/小さい同士なので答えは3 v ^ v ^ v ^ v v ←←←←←|→→→→→→→→ ←iからの探索範囲 x x x x x
また、愚直に調べる対象となる i は、小→中(i)→大 と並んでいるので、 他の要素から見れば、i(の周辺 \pm1)で大/小が連続し、そこで探索が終了することになる。
よって、A の各要素が愚直に調べられる回数は、左方向・右方向に1回ずつとなり、O(N) で間に合う。
小 中 大 大 中 小 A 7 14 6 8 10 2 9 5 4 ... v ^ v ^ v ^ v v |→→→→→→→→ ←←←←←←|
D - Bracket Walk
問題
- N 頂点 M 辺の有向グラフ
- 自己辺・多重辺は存在しない
- 全体が強連結(どの頂点からどの頂点へも経路が存在する)
- 各辺には、'(' または ')' のいずれか一方が書かれている
- 以下のようなウォーク(同じ辺・頂点を複数回通ってもよい移動経路)が存在するか判定せよ
- 始点と終点が同じ頂点
- 全ての辺を1度以上通る
- 通った辺に書かれた括弧を順に並べると、正しい括弧列になっている
- 2 \le N \le 4000
- N \le M \le 8000
解法
以下、単に「ウォーク」というと、始点と終点が同じ「閉じたウォーク」を指すとする。
条件の整理
まず、正しい括弧列というと以下の条件をともに満たすものだが、
- '(' と ')' が同数
- 途中で ')' の個数が '(' の個数を上回ってはいけない
このうち、後者は無視できる。
もし途中で ')' の個数が上回ってしまっても、開始位置を適切にずらすことで、必ず正しい括弧列にできる。
よって、「'(' と ')' が同数」で全辺を通るウォークを見つけさえすればよい。
同数とは、「'(' を +1、')' を -1 で置き換えて、ウォークのコストが0」と言い換えられる。
条件を満たすウォークがあったとして、存在を見つける上では頂点 1 を始点終点に固定して考えて問題ない。
1 を始点と終点とするウォークを Walk_1 とする。
Walk_1 を複数個並べたものもまた、Walk_1 である。
答えとなる「全辺を通りコストが0の Walk_1」を、
Walk_1 の結合で作ることを考える。
コストの正負による分類
ウォークのコストは、当然ながら 正、0、負 のいずれかである。
グラフ中にコスト正の閉路があることと、コスト正の Walk_1 があることは、一致する。
- ⇒の証明
- 閉路が 1 を含んでいればそれ自体がウォークである
- 1 を含まなくても、強連結なので、1 から閉路まで行き、好きなだけ回って帰る、ということができる
- ⇐の証明
- 閉じたウォークは、複数の閉路に分解できる(同じ頂点に2回以上訪れていたら、そこで分割することを繰り返せばよい)
- コストが0や負の閉路だけでは、明らかにコスト正のウォークは構成できない
コスト正・負のウォークがそれぞれあるとどうなるか考える。
正と負が両方存在する場合
条件を満たすウォークを実現できる。
- 未使用辺を1つ適当に取る
- それを含む Walk_1 を適当に1つ決める
- Walk_1 のコストが正なら、別のコスト負の Walk_1、負なら正の Walk_1 を適当に1つ決める
- 2つを何回かぐるぐるすることで、総コストを0にあわせられる
- 2つの Walk_1 で使用した辺を使用済み辺として、最初に戻る
- 全ての辺を使用したら完了
全ての閉路のコストが0の場合
意図的に作らない限り珍しいが、これもあり得る。
この場合も、当然コスト0を実現できる。
正か負のどちらか一方のみが存在する場合
なんとなく「No」っぽいが、証明しようとすると悩ましい。公式Editorialでは背理法による証明がある。
正の閉路があり負の閉路が無いグラフでも、条件を満たすウォークがあったと仮定する。
あるウォーク W と、そこに含まれる辺のみを使用した閉路 C があるとする。
この時、以下のようにすることを、“W から C を取り除く”と表現するとする。
- W で使われた辺と頂点を取り出し、複数回通った辺はその分、多重化したグラフを G_W とする
- G_W は、オイラーグラフ(全ての頂点の入次数と出次数が等しい)となる
- ここから C に使われた辺を取り除いても、G_C もオイラーグラフのため、入出次数の一致は保たれる
- 連結性は保たれないかもしれないが、複数のウォークの集合として表現できる
- 今回は、コストが正か負かのみに注目するため、通過順は変化しても気にしない
条件を満たすウォークを W とし、そこから正の閉路 C を取り除くと、残ったグラフの総コストは負になる。
つまり、どれかのウォークはコストが負にならざるを得ず、負の閉路が存在することになる。
矛盾するので、コスト正の閉路しか無ければ、正解ウォークは作れないことが分かる。
負閉路検出はBellman-Fordで、O(NM) でおこなえる。
正閉路も、コストを逆転させれば同様に求められる。