AtCoder Beginner Contest 149 D~F問題メモ

AtCoder Beginner Contest 149

今年も終わり。

D - Prediction and Restriction

問題

  • じゃんけんマシンと $N$ 回対戦する
  • じゃんけんマシンの各回で出す手は決まっていて、文字列 $S$ で与えられる
  • グーで勝つと $R$ 点、チョキで勝つと $S$ 点、パーで勝つと $P$ 点もらえる。あいこと負けは0点
  • 各対戦で、$K$ 回前に出した手と同じ手を出すことは禁止されている。最初の $K$ 回は好きに出してよい
  • 得点を最大化せよ
  • $2 \le N \le 10^5$

解法

貪欲に勝てる手を出していく。

$K$ 回前の禁止ルールに引っかかり勝てる手を出せない場合のみ、仕方ないので違う手を出す。

この時、2通りの出し方があるので、さらに $K$ 回後の勝利を邪魔しないような手を選ぶことができる。

K回前,今,K回後が同じ場合
        |<-- K回 --|-- K回 -->|
マシン  パ         パ         パ
挑戦者  チ       グorパ       チ
                   ↑
        K回前と異なる2つの手のどちらを出してもK回後に勝てる手を出せる

K回前,今が同じで、K回後が異なる場合
        |<-- K回 --|-- K回 -->|
マシン  パ         パ         チ
挑戦者  チ         パ         グ
                   ↑
        K回前と異なり、かつK回後に勝つための手も邪魔しない手を選べる
  • じゃんけんマシンの $K$ 回前が違う手の場合、勝てる手の得点を加算
  • じゃんけんマシンの $K$ 回前が同じ手で、その時に得点を得ている場合は、禁止ルールで0点
  • じゃんけんマシンの $K$ 回前が同じ手で、その時に0点の場合は、勝てる手の得点を加算

n, k = map(int, input().split())
r, s, p = map(int, input().split())
t = input()
ans = [0] * n
for i, c in enumerate(t):
    if i >= k and c == t[i - k] and ans[i - k] > 0:
        continue
    if c == 'r':
        ans[i] = p
    elif c == 's':
        ans[i] = r
    else:
        ans[i] = s
print(sum(ans))

E - Handshake

問題

  • 長さ $N$ の数列 $A_1,A_2,\ldots,A_N$ が与えられる
  • ここから2要素の組 $(A_i,A_j)$($i=j$ でもよい、順番も区別する)の選び方は $N^2$ 通りある
  • 2要素の和が大きい方から順に $M$ 組を取った時、$M$ 組の和の合計を求めよ
  • $1 \le N \le 10^5$
  • $1 \le M \le N^2$
  • $1 \le A_i \le 10^5$

解法

実際に全組の和を作ると $N^2$ 個できてえらいことになるので、何かしらまとめることを考える。

「和が $X$ 以上になる組が $M$ 個以上できるか?」を $X$ を固定して考えると、 これは $X$ が小さいほど少なく大きいほど多くなるので、二分探索でちょうど $M$ 個以上作れる境界を探せる。

境界が分かれば、そのような組だけ抽出して和の合計を計算することができる。

$X$ を探す範囲は 2~$A_i$ の最大値x2、1回の判定が $O(N \log{N})$、最後の和の合計の算出も $O(N \log{N})$ より、全部で $O(N \log{N} \log{A_{max}})$ でできる。

(例) X=10 で M=8 個以上できるか?
Ai
12  [12,8,3,1] 相方は4個全て可能
 8  [12,8,3]   相方は3通り
 3  [12,8]     相方は2通り
 1  [12]       相方は1通り
               →計 10組の合計が 10以上になる

注意すべきは、$M$ 個以上作れる境界が $X$ であっても、同率があるため和が $X$ である組全てを採用できるわけでは無いこと。

M = 4
組の和(降順) 10, 9, 9, 8, 8, 8, 7, ...
             ~~~~~~~~~~~~
             X=8だが、そのうち採用できるのは1個

よって $X$ を求めた後、和の合計を求める段階では、 まず「和が $X+1$ 以上である組の数」を求めた上で、$M$ に足りない分だけ和が $X$ の組を採用すればよい。

from bisect import bisect, bisect_left
from itertools import accumulate


def check(x):
    count = 0
    for a in aaa:
        if x <= a:
            count += n
        else:
            count += n - bisect_left(aaa, x - a)
    return count >= m


n, m = map(int, input().split())
aaa = list(map(int, input().split()))
aaa.sort()
acc = [0] + list(accumulate(aaa))
sum_a = acc[-1]

l, r = 0, 200001
while l + 1 < r:
    mid = (l + r) // 2
    if check(mid):
        l = mid
    else:
        r = mid

# 合計がlより大きい組み合わせの和と組数を求める→Mに足りない分だけ合計がlの組を採用
ans = 0
count = 0
for a in aaa:
    if l <= a:
        ans += sum_a + a * n
        count += n
    else:
        omit = bisect(aaa, l - a)
        if omit < n:
            ans += sum_a - acc[omit] + a * (n - omit)
            count += n - omit
ans += l * (m - count)
print(ans)

F - Surrounded Nodes

問題

  • $N$ 頂点の木があり、構造も与えられる
  • 各頂点を $\frac{1}{2}$ ずつの確率で白か黒に塗る
  • 黒く塗られた頂点を全て含むような最小の全域木を $S$ とする
  • $S$ の中に含まれる白い頂点の個数の期待値を求めよ
  • $2 \le N \le 2 \times 10^5$

解法

「$S$ に含まれる白い頂点の個数の期待値」は、切り口を変えて「自分が『$S$ に含まれる白い頂点』となる確率」を全頂点について求め、合計したものに等しくなる。 和の期待値=期待値の和。

条件

どのような頂点が「$S$ に含まれる白い頂点」となるか?

  • 自身が白いこと
  • 子の部分木のうち、黒い頂点が1つでも含まれるものが、2つ以上あること

2番目の条件については、自身が $S$ に含まれるための条件となる。

     子1    自   子3
○ -- ○ -- ○ -- ● -- ○ -- ●
            |
         子2○ -- ○

子1~3の部分木のうち、黒い頂点を含むものが1個しか無い場合、$S$ はその部分木の中だけで完結してしまうので、自身は $S$ に含まれない。

一方、2個以上あれば、その黒い頂点同士を結ぶ過程で必ず自身も $S$ に含まれる。

確率

次に、その時の確率を考える。

長いので、説明上「黒い頂点が1つでも含まれる部分木」を「$B$」と表す。

「子の部分木の内、$B$ が2つ以上含まれる確率」は、余事象「$B$ が1つ以下しかない確率」の方が計算しやすいので、これを1から引くことで求める。

ある $k$ 個の頂点について、そいつらが全て白である確率は $\displaystyle \frac{1}{2^k}$、 1つでも黒が含まれる確率は $\displaystyle 1-\frac{1}{2^k}$ となる。

よって、たとえば子1~3の頂点数が $k_1,k_2,k_3$ 個なら、以下の合計となる。

  • $B$ が0個…$\displaystyle \frac{1}{2^{k_1+k_2+k_3}}$
  • $B$ が1個
    • 子1が $B$ … $\displaystyle (1-\frac{1}{2^{k_1}})\frac{1}{2^{k_2+k_3}}$
    • 子2が $B$ … $\displaystyle (1-\frac{1}{2^{k_2}})\frac{1}{2^{k_1+k_3}}$
    • 子3が $B$ … $\displaystyle (1-\frac{1}{2^{k_3}})\frac{1}{2^{k_1+k_2}}$

この合計は、以下のように変形できる。

  • $\displaystyle \frac{1}{2^{k_2+k_3}} + \frac{1}{2^{k_1+k_3}} + \frac{1}{2^{k_1+k_2}} - (3-1)\frac{1}{2^{k_1+k_2+k_3}} $

これを計算して余事象をとり、自身が白である確率1/2をかけたものが、求めるべき「自分が『$S$ に含まれる白い頂点』となる確率」である。

全方位DP

ある頂点について確率を求めるには、自身から伸びる全方向の部分木の頂点数が必要となる。

しかし、たとえばある頂点を根として探索したとして、1回の探索だけでは「親方向の部分木の情報」がわからない。

     根(仮)
   /  \   ←こっち方面の頂点数はわからない
  ○    ●
  /     /\  ←DFSなどで、各子の部分木の頂点数は求められる
 ???   ○○
         |
         ○

そこで、1回まずDFSで子方向の部分木の情報だけ計算しておいて、2回目で親方向の部分木の情報を与えながら探索すると可能となる。

必要なのは頂点数だけなので、全体 $N$ から自分+子の頂点数の総計を引けば求まるね。

実装の方法

今回の制約では再帰数が $2 \times 10^5$ 近くまであり得るが、DFSを再帰関数で書くと、ほぼ直線のような木の場合にTLEする。 スタックに積んでいく実装では間に合った。

「子の探索が終了した後、それを合計するなど集約して、自身の情報とする」ような探索の場合、再帰の方が書きやすいのだが、なかなか無視できない差が生じる。

import sys


def dfs1(root, links):
    """ まず、適当に根を定め、各ノードの親と、子毎の部分木のサイズを計算しておく """
    parent = [0] * n
    subtree_count = [{} for _ in range(n)]
    q = [(root, -1, 0)]  # (v, p, t) p:親ノード番号  t:0=初来訪, 1=全ての子巡回後
    while q:
        v, p, t = q.pop()
        if t == 0:
            parent[v] = p
            q.append((v, p, 1))
            for u in links[v]:
                if u == p:
                    continue
                q.append((u, v, 0))
        elif p != -1:
            subtree_count[p][v] = sum(subtree_count[v].values()) + 1
    return parent, subtree_count


def dfs2(root, parent, subtree_count, d2, d2s):
    """
    各ノードにつき「自身から伸びる部分木のうち、黒が1つでもある枝が2本以上ある確率」を足していく
    """
    ans = 0
    q = [(root, 0)]  # (v, pc) pc:vから見て親方面の部分木のノード数
    while q:
        v, pc = q.pop()

        if len(subtree_count[v]) == 0:
            continue

        p = parent[v]
        children, st_counts = map(list, zip(*subtree_count[v].items()))
        children.append(p)
        st_counts.append(pc)
        cl = len(st_counts)
        ct = sum(st_counts)

        for u, stc in subtree_count[v].items():
            q.append((u, ct - stc + 1))

        if cl == 1:
            continue

        tmp = 0
        for stc in st_counts:
            tmp = (tmp + d2s[ct - stc]) % MOD
        tmp = (tmp - d2s[ct] * (cl - 1)) % MOD
        ans = (ans + (1 - tmp) * d2) % MOD
    return ans


n = int(input())
links = [set() for _ in range(n)]
for line in sys.stdin:
    a, b = map(int, line.split())
    a -= 1
    b -= 1
    links[a].add(b)
    links[b].add(a)
root = 0
MOD = 10 ** 9 + 7
d2 = 500000004  # 2^-1 mod 10**9+7
d2s = [1]
for i in range(n):
    d2s.append(d2s[-1] * d2 % MOD)
parent, subtree_count = dfs1(root, links)
ans = dfs2(root, parent, subtree_count, d2, d2s)
print(ans)

programming_algorithm/contest_history/atcoder/2019/1229_abc149.txt · 最終更新: 2019/12/30 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0