AtCoder Beginner Contest 196 D,E,F問題メモ
D - Hanjo
問題
- H×W のグリッドがある
- この中にA 枚の 1×2 の畳と B 枚の 1×1 の半畳を敷き詰める方法のパターン数を求めよ
- 畳は縦にも横にも使うことができる
- 回転や反転して初めて同じになるものは区別する
- HW=2A+B≤16
解法
「畳」と言ったときは 1×2 の畳を指すものとする。
制約から敷き詰め方を全探索するのだろうとは思うが、実装の仕方にちょっと迷う。
A 枚の畳を敷き詰めると、 余ったスペースへの半畳の敷き詰め方は勝手に決まるので、 半畳は無視して畳の置き方をDFSなどで探索すればよい。
既に各マスに畳が置かれた・置かれていないだけが重要で、畳の種類や縦横などは関係ないので、2HW の情報量で状態を管理できる。
置き方の表現方法としては、H×W の二次元配列でもよいが、 以下のようにすると1つの数字で表現できる。
0-indexでの i 行 j 列目を (i,j) として、i×W+j とすることで 各マスを 0~HW−1 の連番に対応させられるので、 これのbit集合で「畳を置いたマス」を表現する。
<>. 012 876543210 ^.. ↔ 345 → 001001011 = 75 v.. 678
E - Filters
問題
- N 個の関数が与えられる
- i 番目の関数は ai,ti からなり、
- ti=1 のとき、fi(x)=x+ai
- ti=2 のとき、fi(x)=max
- t_i=3 のとき、f_i(x)=\min(x,a_i)
- Q 個の整数 x_1,x_2,...,x_Q が与えられる
- それぞれについて、f_N( ... f_2(f_1(x_i))) を求めよ
- 1 \le N,Q \le 2 \times 10^5
- |a_i|,|x_i| \le 10^9
解法
たとえば \infty,0,-\infty などから始めたときにどうなるかを考えると、
∞ ------ : | 20 | ,------------, : | ,' `, 10 ----' ,------------, `---- ---- : ,' `, ↑ 0 ---------' `---- 答えの : 取り得る -10 ------, 範囲 : | `, ↓ -20 | `---- ---- : | -∞ ------------------- min(10) +10 max(-10) -10
最初に \min(10) が来たら、10~ \infty の数は全て 10 になり、以降の挙動は同じとなる。
以下を管理していけば、
- 答えの取り得る上限と下限
- 上限にも下限にもタッチしなかったら初期値からどれだけずれるか
- つまり全ての t_i=1 であるような a_i の合計 D
x_i+D が上限・下限を超えてしまうような x_i の場合は、上限or下限。 超えていなければ答えは x_i+D。
F - Substring 2
問題
- '0'と'1'からなる2つの文字列 S,T が与えられる
- T のいくつかの文字を変更して、S の部分文字列としたい
- 変更する必要のある最小個数を求めよ
- 1 \le |T| \le |S| \le 10^6
解法
フーリエ変換によるたたみ込み。知っていることが前提になると思われる。
たたみ込みは、原理は何やってるか理解が難しいが、とりあえず以下のことができるアルゴリズム。
- a_0,a_1,a_2,...,a_{N-1} と b_0,b_1,b_2,...,b_{M-1} がある
- そこから以下のような c_0,c_1,...,c_{N+M-2} を作る
- c_k は、i+j=k となるような a_i \times b_j の和
- つまり、\displaystyle c_k = \sum_{i=0}^{N-1} a_i \times b_{k-i}(ただし範囲外の添字については a_i,b_j=0)
愚直には O(NM) かかりそうな所、L=N+M として、O(L \log{L}) で行える。すごい。
C++ なら、ACLに既存の実装があるので自前で実装せずとも利用できる。
Pythonなら、オーバーフローするものは無理だが、今回のように答えが64bitに収まることがわかっている場合は numpy.fft
などを利用できる。
さてこれを今回の問題にどう使うか。
T を S のどの位置に一致させるか決めたら、0か1しか出てこないので、各桁ずつのXORで1となった個数がそのまま答えとなる。
S 10101000010011011110 T 0010011111 XOR 1010001100 合計 4 S 10101000010011011110 T 0010011111 XOR 0000000100 合計 1
S の全ての開始位置について求め、その中の最小を取れば答え。
ここで、T の各文字に逆からindexを振ってみる。
i 01234567890123456789 S 10101000010011011110 T 0010011111 j 9876543210
すると、合わせる位置が同じ |T| 個のペアは i+j が全て等しくなる。
ここにたたみ込みを利用できる。
(i,j)=(4,9),(5,8),...,(13,0) のそれぞれで S_i \oplus T_j を計算して合計した値は、まさしく S の 4 文字目を開始位置としたときの答えとなる。
ただし、高速なたたみ込みができるのはかけ算だが、今必要な演算はXORなので、何らかの変換が必要になる。
「0,1 間のXOR」を「1,-1 間のかけ算」に変換してやると、結果が見事に対応するので、これを利用する。
たたみ込みで求められる値 c_k は |T| 個の -1,1 の和で、今回はこの中で -1 の個数を求めたい。
よって、\dfrac{|T|-c_k}{2} が -1 の個数、ひいては元のXORの演算での1の個数となる。
c_k の先頭と末尾の各 |T|-1 項は、T が S からはみだすような置き方となってしまうため、除いて考える。
暫定コストの小さい方から経路探索的に1文字ずつ合わせていく0-1BFSで、1ケース以外は全部通ったんだけど、まぁダメだったよね。 反例も比較的簡単に作れちゃうし。