Processing math: 24%

AtCoder Regular Contest 173 A,B,C,D問題メモ

AtCoder Regular Contest 173

ケアレスな実装ミスでタイムロスしちまうのはなおらないねえ。

A - Neq Number

問題

  • ある正整数 X を(先頭に0のつかない)十進法表記した際に、同じ数字が隣り合わない場合、X を“Neq Number” という
  • 小さい方から K 番目の Neq Number を求めよ
  • 1つの入力につき T ケース与えられる
  • 1T100
  • 1K1012

解法

d 桁の Neq Number は何個あるか?」は、簡単に求められる。

  • 一番大きい桁は 19 の9通り
  • 続く d1 個の桁は 09 のうち前の桁と違う数字の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=10Kmax 番目のNeqNumberの桁数を D として(D=1415 くらい)、O(BD) で求められる。

Python3

B - Make Many Triangles

問題

  • 2次元空間上に N 個の点がある
  • これらの点を頂点に持つ(非退化な)三角形をできるだけ多く作りたい
  • 1度使った点は他の三角形では使えないとき、最大何個作れるか、求めよ
  • 3N300

解法

基本的には、N3 個作れるはず。

しかし同一直線上にある3点は、三角形が作れないので選ぶことができない。(作れないのはこの条件のみ)

点同士を結ぶ直線のうち、最も多くの点が並んでいる直線を L、そこに乗っている点の数を C とする。

L 上の点をなるべく多く消費することを考えると、L 上から2点と、それ以外から1点を選ぶことで三角形が作れる。
よって、そうやって作っていっても L 上に点が残る場合、作れる個数は NC 個となる。

答えは、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_xd_y は、GCDで割って互いに素にしておく
  • d_x が負の場合、正にする
  • d_x が0かつ d_y が負の場合、d_y を正にする

こうすると、定数倍の違いを吸収して、3つの整数値で直線を特定できる。

Python3

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_ik とする。

B=(B_1,...,B_{N-1}) を、「A_iA_{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
             |→→→→→→→→
              ←←←←←←|

Python3

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) でおこなえる。
正閉路も、コストを逆転させれば同様に求められる。

Python3

programming_algorithm/contest_history/atcoder/2024/0310_arc173.txt · 最終更新: 2024/03/14 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0