AtCoder Regular Contest 136 B,C,D問題メモ

B - Triple Shift

問題

  • 長さ $N$ の2つの整数列 $A=(A_1,...,A_N),B=(B_1,...,B_N)$ が与えられる
  • $A$ を「隣接する3マスを好きに選び、そこのみでシフトする」ことを繰り返して $B$ に一致させられるか、判定せよ
    • つまり、現在の $A_i,A_{i+1},A_{i+2}$ の値を $x,y,z$ とすると、それぞれ $z,x,y$ で置き換える
  • $3 \le N \le 5000$

解法

操作では、並べ替えは発生するものの値自体は変わらないので、値やその個数が一致してないとNo。

で、制約上、端から順番に、実際に操作をシミュレートして合わせていく、ということが可能そう。
(ある数字を希望の位置まで移動させるのは最大 $O(N)$ で、全部で $O(N^2)$)

それを前提として操作をいろいろ試してみると、パターンが見えてくる。

位置 j の要素を i に持っていくとき(i<j)

■j-i が偶数なら、その間をごそっと移動させることが可能
..., A[i-1], A[i], A[i+1], A[i+2], ..., A[j-1], A[j], A[j+1], ...
↓           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
..., A[i-1], A[j], A[i], A[i+1], A[i+2], ..., A[j-1], A[j+1], ...
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

■j-i が奇数なら、どこか1箇所、入れ替わりが発生する
..., A[i-1], A[i], A[i+1], A[i+2], ..., A[j-1], A[j], A[j+1], ...
↓           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
..., A[i-1], A[j], A[i+1], A[i], A[i+2], ..., A[j-1], A[j+1], ...
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                      ^------^

これでシミュレートすると、最後の2要素のみ、自由に移動できずに残る。
これが $A$ と $B$ で一致すればYes、入れ替わっていればNoとなる。

難しいのは数列内に同じ要素が含まれるとき。どれを端に持っていけばよいか、一意に決められない。

しかし、これは、以下のようにすれば

同じ値 x が含まれる。
端から合わせる過程で、x のどれを持っていくか適当に決めた結果、
最後の2要素が入れ替わって残ってしまった。

A: ..., x, ..., x, ..., b, a
B: ..., x, ..., x, ..., a, b

xの登場順にx1,x2とする。
x1-x2間が偶数なら (x1, x2)、奇数なら (x2, x1) のように
結果的に偶数移動となるように、他の並び順を変化させずに隣まで移動させられる

A: ..., ..., x, x, ..., b, a

x,x ~ b,a 間も、他の並び順を変えずに x の隣まで持ってこれる。
xとbの間が偶数ならbから先に、奇数ならaから先に動かせばよい

A: ..., ..., x, x, b, a, ...

この4数を何回か操作することで、a,bを入れ替えられる

A: ..., ..., x, x, a, b, ...

操作を逆に辿り元に戻せば、Bに一致させられる

A: ..., x, ..., x, ..., a, b

ので、同じ要素が1つでも含まれる場合は、必ず一致させられるとわかる。

よりよい解法

公式解説の通り「転倒数の偶奇」が操作によって不変なので、$A$ と $B$ でそれが一致していればYes。$O(N \log{N})$

Python3

C - Circular Addition

問題

  • 長さ $N$ の数列 $A_0,...,A_{N-1}$ が与えられる
  • 長さ $N$ で全要素が $0$ の数列から以下の操作を繰り返して、$A$ に一致させる最低回数を求めよ
  • 操作
    • 任意に幅 $N$ 以下の区間を選び、一律 $1$ を加算する
    • 区間は、$A_{N-1}$ と $A_0$ がつながっているものとみなしてよい
  • $2 \le N \le 2 \times 10^5$

解法

両端がつながっていない場合

端の $A_0$ から、上り下りの段差数を数えればよい。

(0) 4 1 5 2 3 (0)
       ■
   ■  ■
   ■  ■  ■
   ■  ■■■
   ■■■■■   4+4+1=9
差+4-3+4-3+1-3  3+3+3=9

両端に0を補うと、正と負の合計は一致するはずで、その片方のみの合計が答えとなる。

これは、操作を「差分のどこかに+1を1個、-1を1個配る」と言い換えられることからいえる。
正確には+1する場所は-1する場所より左でなくてはならないが、 全要素が非負であり、どの地点でも「今まで出てきた+1の方が、-1より多い」ことが保証されているので、実質無視できる制約。

今回の場合

操作範囲の左端にすることを「開く」、右端にすることを「閉じる」と呼ぶことにする。

ループ時は、$A_0$ で開いた操作と $A_{N-1}$ で閉じた操作、2回を1回の操作に統合できる。

       ■
   ■  ■
   ■  ■  ■
   ■  ■■■
   ■■■■■
   ||  |----|   非ループ時の操作の一部を
   -|  |-----   ループ時は統合できる

統合は最大 $\min(A_0,A_{N-1})$ 回おこなえるので、 非ループ時の答えから $\min(A_0,A_{N-1})$ を引けばよい?

……というのはダメで、 「非ループの時点で全体に対する操作を最適解に含まざるをえない」ものは、最初から1回の操作なので減らせない。

    3 2
   ■
   ■■
   ■■
   |--|    非ループでもループでも1回

統合できる操作というのは、より正確には「途中で一度閉じ、そこより右で再度開かれた2つの操作」となる。

よって、端から「閉じられた回数」と「それより右で開かれた回数」をDPのように求めていく。
この最大ペア数を $X$ とすると、$\min(X,A_0,A_{N-1})$ が、非ループ時から減らせる最大値となる。

本当に $X$ を達成できるの? ループ時に統合するには、「$A_0$ と閉じられた箇所」「開かれた箇所と $A_{N-1}$」それぞれがつながっている操作でなければならないけど、それ確認してなくない? というのがちょっと引っかかる。

たとえば $A_0~A_i$ の区間 $\times d$ 回の操作を統合させたいとして、 その間にボトルネック $A_{b} \le d$ があったりすると、$d$ 回の操作を確保できない。

  ■          ■    A0~Aiの操作を3回、統合したいとして、
  ■          ■    途中にAb=2があると、実際にはできない
  ■ .. ■ .. ■
  ■ .. ■ .. ■■
  A0    Ab    Ai

しかしこういう場合には $A_0$ から $A_b$ までの間に $A_0-A_b$ 回分の閉じられた箇所があるはずで、 非ループ時にそっちで閉じた操作をループ時に統合することにしてしまえばよい。

どこで閉じた操作とどこで開いた操作を統合するか、というのは具体的に求める必要は無いが、 両端から貪欲にマッチングさせていくことで、統合の最大回数 $\min(X,A_0,A_{N-1})$ を達成できる。

Python3

D - Without Carry

問題

  • $N$ 個の整数 $A_1,...,A_N$
  • このうち、相異なる2つのペアで、筆算により繰り上がりが1度も発生しないものの個数を求めよ
  • $2 \le N \le 10^6$
  • $1 \le A_i \lt 10^6$

解法

$A_i$ の範囲が重要で、最大でも6桁しかない。

で、ある数、たとえば “12345” とペアにして繰り上がりが発生しないものとは、

  • 一の位が4以下
  • 十の位が5以下
  • 百の位が6以下
  • 千の位が7以下
  • 一万の位が8以下
  • 十万の位が9以下

であればよい。

3桁の場合で考える

仮に $A_i$ が3桁までとして3次元の座標で考える。

前処理として、“345” が与えられた整数に含まれれば格子点 $(3,4,5)$ に+1する、みたいなことをしておく。

“345” とペアになれる個数は、$(0,0,0)$ と $(6,5,4)$ を対頂点にもつ直方体に含まれる格子点の値の合計となる。
ただし自身+自身で繰り上がりが発生しないものについては、自身とのペアを除く。

3次元で累積和を取っておけば、各整数につきペアにできる値の個数をすぐに求めることができる。

6桁でも、6次元で同じことをすればよい。

実装

……のだが、6次元累積和ってどうすればいいの?と一瞬困る。

まぁ、6個の次元について順番に、他の5次元を総当たりで固定して累積和を取っていけばいいんだが、下手すると冗長なコードになる。

高速ゼータ変換を使える。

ゼータ変換というと、bit集合を使った問題で出てきて「自身を部分集合に持つ集合全体の値の総和」みたいなのを 機械的に計算できるアルゴリズムだが、今回のような多次元の累積和にも活用できる。

というか、bit集合も要は「$(0,1)$ の2値を取る次元が16個くらいあって、各次元について累積和を取る」ので、 累積和がゼータ変換の元々の意味合いともいえる。

Python3

programming_algorithm/contest_history/atcoder/2022/0227_arc136.txt · 最終更新: 2022/03/02 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0