AtCoder Regular Contest 196 (Div. 1) A,B,C,D問題メモ

A - Adjacent Delete

問題文

  • 長さ NN の数列 A=(A1,A2,,AN) が与えられます。
  • あなたはこの数列の隣接する 2 数を選びどちらもこの数列から削除する、という操作を数列の長さが 1 以下になるまで繰り返します。
  • 一度の操作で得られるスコアは選んだ 2 数の差の絶対値です。
  • 得られるスコアの合計としてありうる最大値を求めてください。

制約

  • 2N3×105
  • 1Ai109
  • 入力はすべて整数

解法

分かってみれば単純なんだけどね。本番中は違う方針に走って時間かかっちゃった。

K=N2 とする。操作は K 回おこなうことになる。

A の大きい方から K 要素の多重集合を Sl、小さい方から K 要素の多重集合を Ss とする。

仮に、隣接でなくても自由にペアが作れるとした場合、 達成できる最大値は sum(Sl)sum(Ss) である。
この時「Sl から1要素と Ss から1要素をペアにする」ことを守れば、具体的なペアの組み方は関係ない。

N が偶数の時、この最大値は必ず達成できる。
なぜなら、常に「Sl の要素と Ss の要素が隣接する箇所」は存在するから。それを選び続ければいい。

奇数の場合が問題だが、これも「残す1要素」を固定すれば、左右に分割した長さ偶数の2区間それぞれで同じことが言える。
以下の2つがわかれば、奇数の i に対して Li1+RNi が、Ai を残すときの最大値となる。

  • Li:= 先頭 i 要素だけで Sl,Ss を考えたときの、sum(Sl)sum(Ss)(偶数の i に対してのみ定義)
  • Ri:= 末尾 i 要素だけで Sl,Ss を考えたときの、sum(Sl)sum(Ss)(偶数の i に対してのみ定義)

Ss,Sl をそれぞれ heapq などで実装して、 先頭から常に2つの要素数が均一になるように調整しながら Ai を加えていけば、Li を作れる。
末尾からも同様に Ri を作れば、答えが求められる。

Python3

B - Torus Loop

問題文

  • HW 列のグリッドがあり、各マス Si,j には以下の2種類のタイルのいずれかが置かれています。
    • 種類 A:タイルの表面に、隣接する 2 辺の中点どうしを結ぶ線分が一本描かれている
    • 種類 B:タイルの表面に、向かい合う 2 辺の中点どうしを結ぶ線分が一本描かれている
    • (具体的な絵は問題ページ参照)
  • タイルは自由に回転可能としたとき、タイルを配置する方法の数は、A のタイルの数 a、B のタイルの数 b を用いて、4a×2b 通りあります。
  • このうち、グリッドをトーラスとして見たときにタイルに描かれた線分が行き止まりを持たないようにタイルを配置する方法の数を 998244353 で割ったあまりを出力してください。
  • T 個のテストケースが与えられるので、それぞれについて解いてください。

制約

  • 1T105
  • 2H,W
  • HW106
  • Si(0iH1) は A と B のみからなる長さ W の文字列
  • すべてのテストケースにわたる HW の総和は 106 以下
  • T,H,W は整数

解法

ペンシルパズルなんかを解くときによくある考え方だが、 「ここには線が通る」という情報と同じくらい、「線が通らない」という情報も重要である。

┼  ┼  ┼  ┼  ┼  ┼    この、┼ に挟まれたタテヨコの各隙間に、
  B  A  A  B  B      「線が通る」か「通らない」かを(矛盾なく)決めれば、
┼  ┼  ┼  ┼  ┼  ┼    タイルの向きは一意に決まる。
  A  A  B  A  A
┼  ┼  ┼  ┼  ┼  ┼    タイルの向きの代わりに、各隙間に
  A  B  A  A  A      「線が通る」「通らない」を決めることを考える。
┼  ┼  ┼  ┼  ┼  ┼    ただし、一番上の行と一番下の行、一番左の列と一番右の列は、
                          トーラスなので一致している必要がある。

左上のBに横向きに線を引くと仮定すると、以下まで連鎖的に決まる。

┼×┼  ┼  ┼×┼×┼
━B━A×A━B━B━
┼×┼  ┼  ┼×┼×┼
  A  A  B  A  A
┼┃┼  ┼  ┼┃┼┃┼
  A  B  A  A  A  
┼×┼  ┼  ┼×┼×┼

縦向きに線を引くと仮定すると、全てが反転した形で同じ箇所が以下のように決まる。

┼┃┼  ┼  ┼┃┼┃┼
×B×A━A×B×B×
┼┃┼  ┼  ┼┃┼┃┼
  A  A  B  A  A
┼×┼  ┼  ┼×┼×┼
  A  B  A  A  A  
┼┃┼  ┼  ┼┃┼┃┼

隙間に線が通るか通らないかに着目したとき、各タイルは以下の性質を持つ。

  • タイルAの性質
    • 左に線が通る(━)ことが決まったら、右は通らない(×)ことが決まる。
    • 左に線が通らない(×)ことが決まったら、右は通ること(━)が決まる。
    • 左右が決まっても、上下は決まらない。
  • タイルBの性質
    • 左に線が通る(━)ことが決まったら、右も通り(━)、上下は通らない(×)ことが決まる。
    • 左に線が通らない(×)ことが決まったら、右も通らず(×)、上下は通る(┃)ことが決まる。
  • ※それぞれ、左右や上下を入れ替えても同じことが言える。

つまり、ある行における1つの隙間を「×」か「━」か決めれば、以下のことが発生する。

  • 同じ行の隙間は全て「×」か「━」か決まる
  • その行にBがあると、Bがある列も全て「×」か「┃」か決まる。
  • さらにその列に他のBがあると、その行も全て「×」か「━」か決まる。
  • …というのが連鎖的に繰り返される。

ここで、矛盾するケースが2つある。

  • Aが奇数個あるような行または列が1つでもある場合
    • 1つの行・列内で、Aによる━/×の反転はちょうど偶数回発生しないと、1周したときに合わなくなる。
  • Bが矛盾する場合
┼┃┼  ┼×┼…    左上のBを仮定して通る通らないを決めていった結果、
×B×A━B━      ここのBが矛盾
┼┃┼  ┼×┼…    │
×B×A━A        │
┼┃┼  ┼┃┼…    │
×B×A━B?    ←┘
┼┃┼  ┼?┼…
:  :  :  :

同じ行・列内にある2つのBは、間に挟まれるAの個数によって、一方の向きが決まったとき、もう一方が同じ向きか違う向きか決まる。 この時、行からの制約と、列からの制約が、矛盾することがある。
二部グラフ判定やポテンシャル付きUnionFindなどで、判定をおこなえる。

矛盾が無い場合、各行・各列を1頂点とした H+W 頂点のグラフを考える。
(i,j) がBだったとすると、行 i と列 j を連結にしていく。

連結になった行・列は、どこか1箇所の通る通らないが決まれば全て決まる。
一方、連結でない行・列同士は独立に決められる。

最終的な連結成分の個数を d とすると、2d 通りの決め方がある。

Python3


考察の最初の方にBの矛盾条件に気付いていたはずなのに、詰めていくうちにいつの間にかどっか行ってた。ううむ。

C - Strongly Connected

問題文

  • 2N 頂点 2N1 辺の有向グラフがあります。
    • 頂点には 1,2,,2N の番号がついており、i 番目の辺は頂点 i から頂点 i+1 に向かう有向辺です。
    • N 個の W と N 個の B からなる長さ 2N の文字列 S=S1S2S2N が与えられます。
    • 頂点 iSi が W のとき白、B のとき黒で塗られています。
  • あなたは以下の一連の操作を行います。
    • 2N 個の頂点を白の頂点と黒の頂点 1 つずつからなる N 組のペアに分ける。
    • 各ペアについて白の頂点から黒の頂点に向かう辺を追加する。
  • 操作において頂点を N 組のペアに分ける方法のうち、最終的に得られるグラフが強連結となるような方法の数を 998244353 で割ったあまりを出力してください。

制約

  • 1N2×105
  • SN 個の W と N 個の B からなる長さ 2N の文字列
  • N は整数

解法

B問題と比べて一気に難易度が上がる。
以下のアルゴリズムの組合せとなる。特に分割統治FFTで、配列を変換してからおこなうタイプは初めて見た。

  • 包除原理DP
    • 特に、DP[i] から DP[j] への遷移時、ij 間で1単位採用するパターン数を c として、DP[j]+=1×DP[i]×c みたいに負で寄与させることで、個数の次元を省略できるやつ
  • 分割統治FFT
    • 分割統治FFTは「DP[i] から DP[j] への遷移時、ji が一緒なら遷移係数は一緒」という場合に使われるのがよくある
    • 今回はそれが満たされないが、FFT前に一度情報を変換してからおこなうと上手くいく

包除原理DP

S の各文字間は 2N1 個ある。
この文字間を境に右から左に遡れないとき、その文字間は「切れ目」であるとする。

i 0   1   2   3   4   5   6   7   8    (両端にも i=0 と i=2N を便宜的に追加し、必ず切れ目と扱う)
   ❶→②→③→❹→❺→⑥→❼→⑧
    ^--'    `--^---^   |    ^--'       ○:白 ●:黒   
               `-------'
   切れ目↑  ↑切れ目    ↑切れ目

文字間の集合 S={s1,...,sm} が切れ目であり、他は切れ目であってもなくてもよいパターン数を f(S) とすると、 包除原理で全ての 1,...,2N1 の部分集合に対して (1)|S|f(S) が答えとなる。

当然これはそのまま求められないが、以下の包除原理DPによって求められる。

  • DP[i]:=i 番目の文字間まで見て、i が切れ目であるような状態の中で、
    • 0,1,...,i の中で S として採用した個数が「偶数個の状態数 - 奇数個の状態数」

i が切れ目である状態」とはどういう状態かを考えると、

  • i より左の黒が、全て i より左の白とペアになった状態

といえる。白に関しては i より右の黒とペアになっていても良い。
状態数は、i より左の白、黒の個数を Wi,Bi として、WiPBi=Wi!(WiBi)! で表せる。
ただし、Wi<Bi の場合は、値は 0 とする。

これを踏まえ、DP[j] から DP[i] への遷移を考える。(j<i
切れ目 ji 間にある BiBj 個の黒を、i より左の白とペアにする方法を数えればよい。
ただし、白のうち Bj 個は、既に DP[j] において j より左の黒とのペアが確定している。

その方法の数は、WiBjPBiBj=(WiBj)!(WiBi)! となる。
(いずれかの階乗の中身が負となる場合は、値は 0 とする)

結局、遷移は、

  • DP[i]=1×i1j=0DP[j]×(WiBj)!(WiBi)!

となる。これで O(N2) で解くことはできるようになった。
これを分割統治FFTで高速化する。

分割統治FFT

よくある分割統治FFTは、遷移が以下のようになっていて、

  • DP[i]=i1j=0DP[j]×Cij

この Cij が、ij が同じなら i,j によらず一定というのが重要な性質だった。

今回は、そのままでは成り立たないが、Wi,Bi 基準でまとめなおすと良い形になっている。

Wi=w となるような位置 i のDP結果」は、 「Bj=b となるような、i 未満の全ての位置 j のDP結果の総和」を dpb として、

  • DP[i]=1×b=0dpb(wb)!(wBi)!=1(wBi)!b=0dpb(wb)!

より、dpb(wb)! の部分でたたみ込める。

分割統治FFTにおいても、[l,m) から [m,r) への寄与を求める際に、

  • [l,m) のDP結果を dpb を表す数列に変換する
    • そのままだと b=0Bl1 の部分は 0 が埋まり無駄があるので、indexを左にずらし必要な箇所だけ作る
  • 階乗の数列と畳み込む
    • 畳み込んだ結果を cnvw とする。
  • [m,r) の各 i につき、cnvWiWiBi を計算し、DP[i] に加算する

とすることで、O(N(logN)2) で計算できる。

Python3

D - Roadway

問題文

  • 1,2,,NN 個の町がこの順に一列に並んでいます。
  • また、隣り合う町どうしを結ぶ N1 本の道があり、道 j(1jN1) は町 j と町 j+1 を結んでいます。
  • あなたは各道 j について、強さ wj(非負とは限らない整数)を設定することができます。
  • 人が道を通るとその人の体力が変化します。具体的には、体力が x の人が道 j を通ると、体力が x+wj に変化します。
    • 道を通ることによる影響以外での体力の増減はないものとします。
  • M 人の人がこれから町の間を移動します。人 i(1iM) の要求は以下です。
    • Si を出発し、最短経路で町 Ti に行く。
    • Si を出発する時と町 Ti に到着する時は体力がちょうど 0 で、それ以外の町にいる時は常に体力が正整数であってほしい。
  • ここで、|SiTi|>1 が保証されます。また、ij ならば (Si,Ti)(Sj,Tj) です。
  • 以下の Q 個のクエリを処理してください。
  • k(1kQ) 番目のクエリでは、人 Lk,Lk+1,,Rk の要求を全て満たすように道の強さを設定する方法がある場合 Yes を、ない場合 No を出力してください。

制約

  • 3N4×105
  • 1M2×105
  • 1Q2×105
  • 1Si,TiN
  • |SiTi|>1
  • (Si,Ti)(Sj,Tj)(ij)
  • 1LkRkM
  • 入力はすべて整数

解法

「区間の列」の区間を考えるという、こんがらがりそうな問題。
必要十分条件の整理と証明は大変だけど、「実装しなきゃいけないこと」はC問題よりは理解しやすいかも。

まず、解法の大枠として、「要求が競合する2人組」というのが何個か Si,Ti によって決まっているので、 人の区間 [Lk,Rk] の中に競合する2人組が含まれますか(No)?含まれませんか(Yes)? に答えられればよい。

競合の最小条件に3人以上が絡むことはない。 つまり「人 (i,j,k) が、(i,j),(j,k),(i,k) はそれぞれ共存可能だが、 3人が集まってはじめて競合する」というようなことはない。
まぁ直感的にそんな気はするが、証明しようとすると手間取る。

証明はひとまず後回しで上記が正しいとして、以下のような C が求められれば、

  • Ci:=i から始まる人の区間を伸ばしていったとき、はじめて競合する人
    • ※人 i 自身が競合するとは限らない。人 i を左端とした区間に競合ペアがいるかどうか
    • ※存在しなければ

クエリは CLk>Rk かどうかで判定できる。

なので、Ci の求め方を考える。

競合条件

公式Editorialのように 「辺への重みの割り当て」を「頂点へのポテンシャルの割り当て」に読み替えた方が 条件の列挙はやりやすそう。

  • 各頂点に v1,...,vN のポテンシャルを割り当てる
  • (i,i+1) を結ぶ辺の重みは、wi=vi+1vi とする。

こうすると、要求は以下のように言い換えられる。

  • (A) →方向に向かう人の要求:
    • vSi=vTi
    • 全ての j=Si+1,...,Ti1 に対して、vj>vSi
  • (B) ←方向に向かう人の要求:
    • vSi=vTi
    • 全ての j=Si+1,...,Ti1 に対して、vj<vSi

まず、2人の要求が「内側で起終点が被る」場合は競合する。

方向が同じ(A同士、B同士)場合、起点同士、または終点同士が被るとダメ。

  Si-------->Ti
  Sj-------------->Tj
  
  j にとって、移動中に経由する頂点のポテンシャルは
  常に v[Sj] より大きくないといけないのに、
  v[Ti] = v[Sj] となってしまう


方向が違う(AとBの組)場合、一方の起点と一方の終点が被るとダメ。理由はだいたい一緒

  Si---------->Ti
  Tj<--------------Sj


(※外側で被るのは問題ない)
  Si---->Ti             Si---->Ti
         Sj---->Tj             Tj<----Sj

(また、この問題では親切に |Si-Ti|>1 という制約があるので考えなくてよいが、
  例外的に Si-Ti = Tj-Sj = 1 かつ Si = Tj の場合は競合なくポテンシャルを割り当てられる)
  Si->Ti
  Tj<-Si

また「方向が同じで、区間が交差する」場合も競合する。

  Si------->Ti
      Sj--------->Tj
  
  j にとって、移動中に経由する頂点のポテンシャルは
  常に v[Sj] より大きくないといけないのに、
  v[Si] = v[Ti] < v[Sj] = v[Tj]  であり、v[Ti] が矛盾

条件はこれで全てである。

略証

実装方法

Ci は尺取法で求められる。

lr1 までが競合していないとして、そこに人 r を追加しようとしたとき、競合するかを判定できればよい。

内側で起終点が被っていないかどうかは、 「方向→/←別」「起点/終点別」の4種別で 現在起終点として使われている町の番号を記録しておけばよい。

区間が交差しないかどうかは、方向→/←別に、Zobrist Hash を用いて判定できる。
人ごとにランダムな数値を決め、Si,Ti に同じ値をXORしておく。
[Sr+1,Tr1] 間のXORをとれば、「r の範囲内に一方の端だけを含む区間(=r と交差する区間)」が存在した時に XORが 0 以外になり、判定できる。

(考えてみれば当然なものの)尺取法実装時の注意点として、 r を伸ばしていくときは r 自身が追加できるかどうかを判定すればよいが、 l を縮めていくときも、「最後に追加しようとして競合した r が、l を除外していって追加可能になるかどうか」を判定するのであって、「l が競合する」かどうかを判定するのではない。
なんか、l の判定にも r が登場するのがちょっと珍しく感じた。

Python3

programming_algorithm/contest_history/atcoder/2025/0406_arc196.txt · 最終更新: by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0