AtCoder Beginner Contest 179 D,E,F問題メモ

AtCoder Beginner Contest 179

モノポリーは刑務所過ぎたオレンジあたりの土地が強い。知らんけど。

D - Leaping Tak

問題

  • 一列に並んだ $N$ マスを、1から $N$ まで移動する
  • 移動の方法は、以下の通り
    • 共通部分を持たない $K$ 個の区間 $[L_1,R_1],[L_2,R_2],\ldots,[L_K,R_K]$ が与えられる
    • 1回に進めるマスの数は、このどれかに含まれる数に限られる
    • $d$ を選んだ場合、現在地が $i$ なら $i+d$ に移動する
  • 1から $N$ まで移動する方法の個数を $\mod{998244353}$ で求めよ
  • $2 \le N \le 2 \times 10^5$
  • $1 \le K \le 10$

解法

累積和DP。

  • $DP[i]=$ マス $i$ への行き方の通り数

として、$i$ の小さい方から順次更新していく。

進めるマスの数の集合を $S$ とすると、一般的なDPでは骨子は以下のようになる。

for i in range(1, N):     # 移動元
    for d in S:           # 何マス進むか
        DP[i+d] += DP[i]

しかし今回の場合、$|S|$ は最大 $N$ 通りになるため、そのままやると $O(N^2)$ となり不可能。

$S$ の要素がある程度連続していることを利用する。

配列 $DC$ を用意し、これの累積和を $DP$ とする。
つまり $DP[i] = DC[1]+DC[2]+\ldots+DC[i]$ となるように、$DC$ の方を管理する。

すると、たとえば「$DP[4]~DP[7]$ に一律に $x$ を足す」操作は、「$DC[4]$ に $x$、$DC[8]$ に $-x$ を足す」操作に置き換えられる。
これを用いて何マス進むかの遷移を高速化する。

Fenwick Treeを用いると累積和の更新・取得を $O(\log{N})$ で管理できるので、$K \le 10$ 通りの区間で1回の取得と2回の更新を行っても、たかだか定数倍。 全体で $O(NK\log{N})$ となる。

import sys


class BinaryIndexedTree:
    def __init__(self, n, MOD):
        self.size = n
        self.tree = [0] * (n + 1)
        self.depth = n.bit_length()
        self.MOD = MOD

    def sum(self, i):
        s = 0
        while i > 0:
            s += self.tree[i]
            i -= i & -i
        return s % self.MOD

    def add(self, i, x):
        mod = self.MOD
        while i <= self.size:
            self.tree[i] = (self.tree[i] + x) % mod
            i += i & -i

    def lower_bound(self, x):
        sum_ = 0
        pos = 0
        for i in range(self.depth, -1, -1):
            k = pos + (1 << i)
            if k <= self.size and sum_ + self.tree[k] < x:
                sum_ += self.tree[k]
                pos += 1 << i
        return pos + 1, sum_


n, k, *lr = map(int, sys.stdin.buffer.read().split())
lr = list(zip(lr[0::2], lr[1::2]))
MOD = 998244353

bit = BinaryIndexedTree(n + 2, MOD)
bit.add(1, 1)
bit.add(2, -1)
for i in range(1, n):
    v = bit.sum(i)
    for l, r in lr:
        bit.add(i + l, v)
        bit.add(min(i + r + 1, n + 1), -v)

print(bit.sum(n))

E - Sequence Sum

問題

  • $A_1=X$、$A_{i+1}=A_i^2 \% M$ である数列について、$\displaystyle \sum_{i=1}^{N}A_i$ を求めよ
    • $A_i$ の要素は $M$ で割った余りをとるが、和を求める部分では割らないことに注意
  • $1 \le N \le 10^{10}$
  • $0 \le X \le M \le 10^5$

解法

$A_i$ はループする性質を利用する。

$A_i$ の要素は最大でも $M$ 通りの値しか取らないし、一度同じ値を取ったらそこからの操作は全く一緒なので全く同じ値が繰り返される。

よって、まずはそのまま $A_1,A_2,\ldots$ を求めていく。

過去に出現した値が再出現したら、ループ位置と長さを求める。

X=2, M=55

      2,  4, 16, 36, 31, 26, 16, 36, 31, ...
    |------||--------------|
    ループ前    ループ
       A           B

ループは $(N-A)/B$(切り捨て)回繰り返され、残り $(N-A)\%B$ 個余る。

たとえば $N=20$ なら、4回ループして2個余るので、以下のように求められる。

  • $(2+4) + (16+36+31+26) \times 4 + (16+36)$

他には、ダブリングで1個先、2個先、4個先、8個先、…の要素を求めていく方法でも解ける。

def solve(n, x, m):
    result = [x]
    checked = [0] * m
    checked[x] = 1
    while len(result) < n:
        x = x * x % m
        if checked[x] > 0:
            break
        result.append(x)
        checked[x] = len(result)
    else:
        return sum(result)

    l = len(result)
    i = checked[x]
    before_len = i - 1
    loop_len = l - i + 1
    loop_sum = sum(result[before_len:])
    d, e = divmod(n - before_len, loop_len)
    ans = sum(result[:before_len]) + loop_sum * d + sum(result[before_len:before_len + e])
    return ans


n, x, m = map(int, input().split())
print(solve(n, x, m))

F - Simplified Reversi

問題

  • $N \times N$ の盤面で、単純化されたリバーシをする
  • 中央の $N-2 \times N-2$ マスには黒が、右端と下端にはそれぞれ白が置かれている
  • 白い石を $Q$ 回置く
    • 置く石は、以下の2通りのクエリで表現される
      • 1 x: 最上段の左から $x$ 列目に置く
      • 2 x: 最左列の上から $x$ 行目に置く
  • リバーシのように、白い石に挟まれた黒い石はひっくり返される
  • 最終的に残っている黒い石の個数を求めよ
  • $3 \le N \le 2 \times 10^5$
  • $0 \le Q \le 2 \times 10^5$
  • クエリは全て異なる

解法

AtCoder-Libraryが発表された折に遅延伝播セグメント木を用意したら、もっと賢い方法はありそうだけどついそれ使っちゃう。

以下の情報を持っておきたい。

  • $H[i]=$ 上から $i$ 列目に今、石を置かれたらひっくり返される黒の個数
  • $V[j]=$ 左から $j$ 列目に今、石を置かれたらひっくり返される黒の個数

はじめはそれぞれ $i=2~N-1$ について $N-2$ で初期化されている。他の $i$ については特に利用しないので未定義でよい。

N=5
    i  2 3 4  H
j 2   ●●●  3
  2   ●●●  3
  4   ●●●  3
V      3 3 3

黒は $(N-2) \times (N-2)$ 個あり、そこからひっくり返される分を引いていく。

以下、縦方向にひっくり返される分について考える。横方向も軸を入れ替えて同様に考えればよい。

縦方向のクエリ

$j$ 列目に置かれたら、その時の $V[j]$ の値だけ黒が減る。他の列に関しては特に影響なし。

横方向のクエリ

縦方向にひっくり返されることは無いが、$V$ を更新する必要がある。

$i$ 行目に置かれると、横方向に $H[i]$ 個ひっくり返される。$k=H[i]$ とする。

するとそれ以降、$V[2]~V[k+1]$ に関しては、「現在の値と $i-2$ の小さい方」に置き換えられる。

  i=4              2 3 4 5 6
  ●●●○●      ●●●○●
  ●●●○●      ●●●○●
  ●●●○●  →  ○○○○●  k=3
  ●●●○●      ●●●○●
  ●●●○●      ●●●○●
  55555      22255

なので、縦方向と横方向のそれぞれで、最小値について1点取得と範囲更新ができるデータ構造を持っておけばよい。

import os
import sys

import numpy as np


def solve(inp):
    SEGTREE_TABLES = []
    COMMON_STACK = np.zeros(10 ** 7, dtype=np.int64)
    INF = 10 ** 18

    def bit_length(n):
        ret = 0
        while n:
            n >>= 1
            ret += 1
        return ret

    def segtree_init(n):
        n2 = 1 << bit_length(n)
        table = np.full((n2 << 1, 2), INF, dtype=np.int64)
        SEGTREE_TABLES.append(table)
        return len(SEGTREE_TABLES) - 1

    def segtree_build(ins, arr):
        table = SEGTREE_TABLES[ins]
        offset = table.shape[0] >> 1
        table[offset:offset + len(arr), 0] = arr
        for i in range(offset - 1, 0, -1):
            ch = i << 1
            table[i, 0] = min(table[ch, 0], table[ch + 1, 0])

    def segtree_eval(table, offset, i):
        lazy_min = table[i, 1]
        if i < offset:
            ch = i << 1
            table[ch, 1] = min(table[ch, 1], lazy_min)
            table[ch + 1, 1] = min(table[ch + 1, 1], lazy_min)
        table[i, 0] = min(table[i, 0], lazy_min)
        table[i, 1] = INF

    def segtree_bottomup(table, i):
        lch = i << 1
        rch = lch + 1
        l_dat = min(table[lch, 0], table[lch, 1])
        r_dat = min(table[rch, 0], table[rch, 1])
        table[i, 0] = min(l_dat, r_dat)

    def segtree_range_update(ins, l, r, mn):
        table = SEGTREE_TABLES[ins]
        offset = table.shape[0] >> 1
        stack = COMMON_STACK
        stack[:3] = (1, 0, offset)
        si = 3
        updated = []
        while si:
            i, a, b = stack[si - 3:si]

            segtree_eval(table, offset, i)

            if b <= l or r <= a:
                si -= 3
                continue

            if l <= a and b <= r:
                table[i, 1] = min(table[i, 1], mn)
                si -= 3
                continue

            updated.append(i)

            m = (a + b) // 2
            stack[si - 3:si] = (i << 1, a, m)
            stack[si:si + 3] = ((i << 1) + 1, m, b)
            si += 3

        while updated:
            i = updated.pop()
            segtree_bottomup(table, i)

    def segtree_query(ins, l, r):
        """ sum [l, r) """
        table = SEGTREE_TABLES[ins]
        offset = table.shape[0] >> 1
        stack = COMMON_STACK
        stack[:3] = (1, 0, offset)
        si = 3
        res = INF
        updated = []

        while si:
            i, a, b = stack[si - 3:si]

            segtree_eval(table, offset, i)

            if b <= l or r <= a:
                si -= 3
                continue

            if l <= a and b <= r:
                res = min(res, table[i, 0])
                si -= 3
                continue

            updated.append(i)

            m = (a + b) // 2
            stack[si - 3:si] = (i << 1, a, m)
            stack[si:si + 3] = ((i << 1) + 1, m, b)
            si += 3

        while updated:
            i = updated.pop()
            segtree_bottomup(table, i)

        return res

    def segtree_debug_print(ins):
        table = SEGTREE_TABLES[ins]
        offset = table.shape[0] >> 1
        for t in range(2):
            i = 1
            while i <= offset:
                print(table[i:2 * i, t])
                i <<= 1

    n = inp[0]
    q = inp[1]
    ops = inp[2::2]
    xxx = inp[3::2]

    ins1 = segtree_init(n - 2)
    ins2 = segtree_init(n - 2)
    init = np.full(n - 2, n - 2, dtype=np.int64)
    segtree_build(ins1, init)
    segtree_build(ins2, init)

    ans = (n - 2) * (n - 2)
    for qi in range(q):
        o = ops[qi]
        x = xxx[qi]
        if o == 1:
            k = segtree_query(ins1, x - 2, x - 1)
            ans -= k
            segtree_range_update(ins2, 0, k + 1, x - 2)
        else:
            k = segtree_query(ins2, x - 2, x - 1)
            ans -= k
            segtree_range_update(ins1, 0, k + 1, x - 2)
        # print(qi, o, x, k, ans)
        # segtree_debug_print(ins1)
        # segtree_debug_print(ins2)

    return ans


if sys.argv[-1] == 'ONLINE_JUDGE':
    from numba.pycc import CC

    cc = CC('my_module')
    cc.export('solve', '(i8[:],)')(solve)
    cc.compile()
    exit()

if os.name == 'posix':
    # noinspection PyUnresolvedReferences
    from my_module import solve
else:
    from numba import njit

    solve = njit('(i8[:],)', cache=True)(solve)
    print('compiled', file=sys.stderr)

inp = np.fromstring(sys.stdin.read(), dtype=np.int64, sep=' ')
ans = solve(inp)
print(ans)

programming_algorithm/contest_history/atcoder/2020/0919_abc179.txt · 最終更新: 2020/09/19 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0