目次

AtCoder Regular Contest 151 C,D,E問題メモ

AtCoder Regular Contest 151

手元の実験で法則性が見えやすい問題がC,Dあたりに置かれていると個人的に解きやすい気がする。

C - 01 Game

C - 01 Game

問題

解法

対戦形式の問題では、とりあえずGrundy数を作れないか確認する。

今回の場合、連続した未書き込みマスを1つの山とみれば互いに山は影響し合わないので、これ毎にGrundy数を設定できそう。

ただ、$N$ がやたら大きい。
Grundy数で解けるとしたら、プログラムで小さい状態からちまちま計算していくのではなく、良い法則があるはず。

頑張って、小さい区間から確認していくと、以下のようなGrundy数の法則が見つかる。

これを証明していく。
便宜的に、Grundy数がkであることをGkと示す。

前提知識として、「操作により山を複数に分割することができるようなゲームにおいては、 分割後の山々のGrundy数のXORを、遷移先のGrundy数として扱えばよい」ことは使われる。

まず、両端が数字に挟まれた区間については、

長さ0
00       ルールに反する書き込み方なので考えなくて良い
01       もう書き込めないのでG0

長さ1
0.0      間に1を書き込んでG0に遷移できるのでG1
0.1      もう書き込めないのでG0

長さ0~k-1の時まで下記が成り立っているとして、
0...0    G1
0...1    G0

長さkの時
0....0   どこに書き込もうと、「G0,G0」か「G1,G1」に分割される。
         →いずれにしろG0のみに遷移できることになり、これ自体はG1となる。
0....1   どこに書き込もうと、「G0,G1」に分割される。
         →いずれにしろG1のみに遷移できることになり、これ自体はG0となる。

数学的帰納法により、間の長さに依らず、同じ数字ならG1、違うならG0とわかる。

一方の端が数字と接する場合、

|0       G0
|.0      G1

区間長 n = 0~k-1 まで、そのGrundy数は Gn であると仮定して、

区間長kの時
|.....0  左からt番目に'1'を書き込むと、「G(t-1),G0」に分割される。
         →G(t-1) に遷移できる。
         →よって 0~k-1 の全てに遷移できるので、これ自体は Gk となる。

h 両端が数字に接しない場合、

偶数の時
|....|    どこに書き込んでも、「G(偶数),G(奇数)」に分割される。
          →このXORは決して0にならないため、これ自体は G0 となる。

奇数の時
|.....|   真ん中に書き込むと、「G(x), G(x)」(同じ値)に分割される。
          →G0に遷移できる。
          どこに書き込んでも「G(偶数),G(偶数)」または「G(奇数),G(奇数)」にしかならない。
          →G1には決してならない
          →これ自体はG1となる。

これで全ての場合が証明できた。

Python3

D - Binary Representations and Queries

D - Binary Representations and Queries

問題

解法

操作の内容ががややこしいが、要は、例えば $N=2$ なら正方形の頂点に数字が書かれていて、

※Ai の添字を2進数で表記

  A10 --- A11
   |       |
  A00 --- A01     

$X_i=0$ なら左右方向の辺、$X_i=1$ なら上下方向の辺が、それぞれ加算する・される関係性を示すことになる。
どちらにどちらが加算されるかを $Y_i$ が示すことになる。

$N=3$ なら3次元の立方体で同様の表現ができる。
$N=4$ 以上だと図で示すのは難しくなるが、次元が増えていく。

で、実験してみると、どうも異なる次元($X_i$)間での操作は、入れ替えても結果が変わらないことに気づく。

a---b      a---(a+b)      a---(a+b)      ※矢印は加算方向
|   |  →  |     |    ↓  |     |
c---d      c---(c+d)    (a+c)-(a+b+c+d)

a---b      a-----b        a---(a+b)
|   |  ↓  |     |    →  |     |
c---d    (a+c)-(b+d)    (a+c)-(a+b+c+d)

一方、同じ次元同士では、操作の順番は入れ替えると結果が変わってしまう。

よって、まず $X_i=1$ のクエリだけを与えられた順番通りに処理、 その後 $X_i=2$ について処理、、、と操作をまとめることができそう。

$X_i$ が同じクエリの操作は順番通りにやるしかないが、 ここで、ある $A_j$ の結果に影響するような他の $A$ の値 というのは、 上記の正方形の例えで互いに辺でつながれた同士、 つまり $j$ に対して $X_i$ 桁目を反転させた $j'$ の位置にある $A_{j'}$ のみである。

$X_i$ 桁目が'0'である方を $j$、'1'である方を $j'$ と固定すると、 $X_i$ に関する全てのクエリ処理後の $(A_j,A_{j'})$ は、 $(a_iA_j+b_iA_{j'}, c_iA_j+d_iA_{j'})$ というように2数に係数をかけた値の和で表すことができて、 この $(a_i,b_i,c_i,d_i)$ は、どの $(j,j')$ に対しても変わらない。

よって、$(a_i,b_i,c_i,d_i)=(1,0,0,1)$ の初期状態から始めて、操作をシミュレーションして係数を求めておけば、

で、$O(N2^N+Q)$ で求めることができる。

Python3

E - Keep Being Substring

E - Keep Being Substring

問題

A:  3  1  4  1  5  7  2
X:  3  1
Y:  1  5  7

    3  1                  先頭を削除
       1                  末尾に5を追加
             1  5         末尾に7を追加
             1  5  7      3回で一致

解法

$A$ の中に同じ連続部分列が複数回出てきたら互いにワープできる、というところが難しい。

ただ、ワープできる文字列は互いに似通っていることから、Suffix Arrayを使うとどこにワープできるかがわかりやすくなりそう。
Suffix Arrayで次に来る文字列と何文字一致しているか、を示すLCP配列も求めておくと、以下のようになる。

A:  6  1  2  7  2  3  4  8  3  4  5  9
↓
 i sa[i] lcp[i]  sa[i]から開始した数列A
 0    1    0     [1 2 7 2 3 4 8 3 4 5 9]
 1    4    1     [2 3 4 8 3 4 5 9]
 2    2    0     [2 7 2 3 4 8 3 4 5 9]
 3    8    2     [3 4 5 9]
 4    5    0     [3 4 8 3 4 5 9]
 5    9    1     [4 5 9]
 6    6    0     [4 8 3 4 5 9]
 7   10    0     [5 9]
 8    0    0     [6 1 2 7 2 3 4 8 3 4 5 9]
 9    3    0     [7 2 3 4 8 3 4 5 9]
10    7    0     [8 3 4 5 9]
11   11    -     [9]

この上で、

とすると、たとえば $X=(3,4,8,3)$ から始まったなら $j=4,k=5,l=4$ である。
複数の場所に $X$ が出てくるかも知れないが、そこは適当な1つを選ぶ。

このとき、操作によって遷移できるのは「A上で伸び縮みさせる」か「SuffixArray上で隣に移動する」かのどちらかと考えてよい。

というような遷移が描ける。これでDijkstraなどで $X$ の位置から $Y$ の位置に最短経路探索し、 $j$ が $Y$ のSuffixArray上の位置まで来たら、あとは末尾に追加して長さを揃えるコストを足して答えが求まりそう。。。

ただ、Dijkstraしようにも、状態が多すぎる。

$j$ と $k$ は表裏一体なので1つにまとめられるとしても、 同じ $j$ に対して「$l$ が短いがコストが軽い」「$l$ が長いがコストが若干重い」みたいな2つの状態が発生しうる。

例えば $Y=(1,2,3,4,5)$ の頂点に「コスト1で $l=1$($[1]$)で到達」「コスト2で $l=3$($[1,2,3]$)で到達」の場合、 最終的に $Y$ まで伸ばすコストも考えると後者の方がよいが、先にDijkstraで探索されるのは前者、のようになる。

これらの扱い方をどうするか。

証明ができていないのだが、 「$Y$ が長いのに短い方が先に探索される」「$Y$ が短いのに長い方が先に探索される」のが問題なので、 ある $j$ を訪れたときに

をそれぞれ記録しておいて、過去にその $j$ を訪れた時の値と比較していずれも悪ければ遷移させない、とすると通った。

でも、単にテストケースに見逃されているだけのような気もする。
$Y$ が中くらいの長さで、短い状態と長い状態が先に探索された場合とか。
ちょっとずつ、同じ頂点が過去に訪れた時より良いコストとなり、同じ頂点が何度も更新されてTLEになるとか。

それともそういう探索はされないという性質があるのかな。

Python3