Loading [MathJax]/jax/output/CommonHTML/jax.js

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

A - Adjacent Delete

問題文

  • 長さ N の数列 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

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