ARC 089
C - Traveling
def check(cx, cy, t, x, y): dx = abs(x - cx) dy = abs(y - cy) if dx + dy > t: return False if ((dx + dy) & 1) != (t & 1): return False return True n = int(input()) ct, cx, cy = 0, 0, 0 for _ in range(n): t, x, y = map(int, input().split()) result = check(cx, cy, t - ct, x, y) if not result: print('No') break ct, cx, cy = t, x, y else: print('Yes')
D - Checker
問題
- 無限のXY座標平面を、グリッド $K$ の市松模様に塗る
- 白黒の境界位置は自由
- 座標 $(x,y)$ を(黒/白)に塗りたいという希望が $N$ 個ある
- 最大いくつの希望を叶えられるか
- $1 \le K \le 1000$
- $1 \le x,y \le 10^5$
K=3だと ■□□□■■ ■■□□□■ ■□□□■■ ■■□□□■ □■■■□□ ■■□□□■ □■■■□□ □□■■■□ □■■■□□ □□■■■□ ■□□□■■ とか □□■■■□ とかの境界位置(塗るパターン)がある
解法
計算量の見積もり
塗るパターンは、境界位置が $x,y$ 座標に各 $K$ 通りに、どちらを黒にするかで2通りあるので、$2K^2$ 通り。この部分は総当たりで調べるしかないと勘で仮定する。
とすると、$K \le 1000$ なので、各パターンにつき $O(1)$ で、叶えられる希望数を求めないと間に合わない。
情報をまとめる
市松模様の特徴、座標の繰り返しを利用する。
- $(x,y)の色 = (x+kK, y+lK)の色$ 、ただし $k,l$ 整数、偶奇一致
- $(x,y)の色 \ne (x+kK, y+lK)の色$ 、ただし $k,l$ 整数、偶奇異なる
つまり、$K=3$ なら、希望は以下のようになる。
- $(5,3)$ を黒にしたい = $(2,0)$ を黒にしたい
- $(5,2)$ を黒にしたい = $(2,2)$ を白にしたい
これで、各希望の座標を $1 \le x,y \lt K$ の範囲にまとめられた。
二次元累積和
「白にしたい」希望と「黒にしたい」希望について個別に $K \times K$ の二次元累積和テーブルを作ると、叶えられる希望数が $O(1)$ で求められる。二次元累積和では、任意の矩形範囲にある要素数をO(1)で求められる。
- 二次元累積和
req_b[x][y]
$=(0,0)$~$(x,y)$ にある「そこを黒にしたい」という希望の数req_w[x][y]
$=(0,0)$~$(x,y)$ にある「そこを白にしたい」という希望の数
すると、ある $x,y$ を市松模様の境界とした時に叶えられる希望数は、
たとえばK=3, x,y=1,2とする パターンA パターンB □■■ ■□□ ■□□ □■■ ■□□ □■■
パターンAで叶えられる希望数は、以下の和となる。
- Aの図で■の範囲にある「黒にしたい」希望数
- Aの図で□の範囲にある「白にしたい」希望数
叶えられる ..■■ ...... ..■■ 黒にしたい = ■.... = ■.... + ...... 希望数 ■.... ■.... ...... ..■■ ■■■ ■.... ...... ...... ...... = ■■■ - ■.... - ■■■ + ■.... ...... ■■■ ■.... ■■■ ■.... 叶えられる □.... □□□ ..□□ 白にしたい = ..□□ = □□□ - □.... 希望数 ..□□ □□□ □.... で、黒と同様に求められる
これを黒白別で求めた合計が、パターンAでの希望数となる。
パターンBで叶えられる希望数は、「Aで叶えられない希望数」となる。
境界位置をずらし、全パターン求め、その最大値が答え。
Python
配列を回して添え字から要素を取得する処理は、Pythonはやや苦手で遅い。しかし二次元累積和を求める際は多用する。
PyPyなら余裕を持って通るが、PythonならTLEだった。でも通してる人もいるので、方法はあるのだろう。(NumPyあたり上手く使えばいいのかな?)
from itertools import accumulate n, k = map(int, input().split()) k2 = k * 2 req_b = [[0] * k for _ in range(k)] req_w = [[0] * k for _ in range(k)] bc, wc = 0, 0 for i in range(n): x, y, c = input().split() x, y = int(x), int(y) rx, ry = x % k2, y % k2 b = c == 'B' if rx >= k: rx -= k if ry >= k: ry -= k else: b = not b elif ry >= k: ry -= k b = not b if b: req_b[rx][ry] += 1 bc += 1 else: req_w[rx][ry] += 1 wc += 1 # 累積和の計算 req_b[0] = list(accumulate(req_b[0])) req_w[0] = list(accumulate(req_w[0])) for i in range(1, k): req_b[i][0] += req_b[i - 1][0] req_w[i][0] += req_w[i - 1][0] for j in range(1, k): req_b[i][j] += req_b[i][j - 1] + req_b[i - 1][j] - req_b[i - 1][j - 1] req_w[i][j] += req_w[i][j - 1] + req_w[i - 1][j] - req_w[i - 1][j - 1] # 各パターン走査 ans = 0 for dx in range(k): dxk_b = req_b[dx][-1] dxk_w = req_w[dx][-1] for dy in range(k): b1 = req_b[dx][dy] b2 = bc - req_b[-1][dy] - dxk_b + b1 w1 = req_w[dx][dy] w2 = wc - req_w[-1][dy] - dxk_w + w1 b, w = b1 + b2, w1 + w2 ans = max(ans, b + wc - w, w + bc - b) # print(dx, dy, b, w, b + wc - w, w + bc - b) print(ans)
itertools.accumulate
の利用と、配列参照を少なくしたらpythonでも通った。
from itertools import accumulate # 入力からreq_b,req_wを作る処理は上と同じ # 累積和の計算 req_b = [list(accumulate(req)) for req in req_b] req_w = [list(accumulate(req)) for req in req_w] req_b = [list(accumulate(req)) for req in zip(*req_b)] req_w = [list(accumulate(req)) for req in zip(*req_w)] # 各パターン走査 ans = 0 kb, kw = req_b[-1], req_w[-1] for dx_b, dx_w in zip(req_b, req_w): dxk_b = dx_b[-1] dxk_w = dx_w[-1] for b1, w1, kyb, kyw in zip(dx_b, dx_w, kb, kw): b = bc - kyb - dxk_b + b1 * 2 w = wc - kyw - dxk_w + w1 * 2 a = b + wc - w ans = max(ans, a, n - a) print(ans)