−目次
AtCoder Beginner Contest 149 D~F問題メモ
D - Prediction and Restriction
問題
- じゃんけんマシンと 回対戦する
- じゃんけんマシンの各回で出す手は決まっていて、文字列 で与えられる
- グーで勝つと 点、チョキで勝つと 点、パーで勝つと 点もらえる。あいこと負けは0点
- 各対戦で、 回前に出した手と同じ手を出すことは禁止されている。最初の 回は好きに出してよい
- 得点を最大化せよ
解法
貪欲に勝てる手を出していく。
回前の禁止ルールに引っかかり勝てる手を出せない場合のみ、仕方ないので違う手を出す。
この時、2通りの出し方があるので、さらに 回後の勝利を邪魔しないような手を選ぶことができる。
K回前,今,K回後が同じ場合
|<-- K回 --|-- K回 -->|
マシン パ パ パ
挑戦者 チ グorパ チ
↑
K回前と異なる2つの手のどちらを出してもK回後に勝てる手を出せる
K回前,今が同じで、K回後が異なる場合
|<-- K回 --|-- K回 -->|
マシン パ パ チ
挑戦者 チ パ グ
↑
K回前と異なり、かつK回後に勝つための手も邪魔しない手を選べる
- じゃんけんマシンの 回前が違う手の場合、勝てる手の得点を加算
- じゃんけんマシンの 回前が同じ手で、その時に得点を得ている場合は、禁止ルールで0点
- じゃんけんマシンの 回前が同じ手で、その時に0点の場合は、勝てる手の得点を加算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
n, k = map(int, input().split())r, s, p = map(int, input().split())t = input()ans = [0] * nfor 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] = sprint(sum(ans)) |
E - Handshake
問題
- 長さ の数列 が与えられる
- ここから2要素の組 ( でもよい、順番も区別する)の選び方は 通りある
- 2要素の和が大きい方から順に 組を取った時、 組の和の合計を求めよ
解法
実際に全組の和を作ると 個できてえらいことになるので、何かしらまとめることを考える。
「和が 以上になる組が 個以上できるか?」を を固定して考えると、 これは が小さいほど少なく大きいほど多くなるので、二分探索でちょうど 個以上作れる境界を探せる。
境界が分かれば、そのような組だけ抽出して和の合計を計算することができる。
を探す範囲は 2~ の最大値x2、1回の判定が 、最後の和の合計の算出も より、全部で でできる。
(例) 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 = 4
組の和(降順) 10, 9, 9, 8, 8, 8, 7, ...
~~~~~~~~~~~~
X=8だが、そのうち採用できるのは1個
よって を求めた後、和の合計を求める段階では、 まず「和が 以上である組の数」を求めた上で、 に足りない分だけ和が の組を採用すればよい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
from bisect import bisect, bisect_leftfrom itertools import accumulatedef check(x): count = 0 for a in aaa: if x <= a: count += n else: count += n - bisect_left(aaa, x - a) return count >= mn, 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, 200001while l + 1 < r: mid = (l + r) // 2 if check(mid): l = mid else: r = mid# 合計がlより大きい組み合わせの和と組数を求める→Mに足りない分だけ合計がlの組を採用ans = 0count = 0for 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 - omitans += l * (m - count)print(ans) |
F - Surrounded Nodes
問題
- 頂点の木があり、構造も与えられる
- 各頂点を ずつの確率で白か黒に塗る
- 黒く塗られた頂点を全て含むような最小の全域木を とする
- の中に含まれる白い頂点の個数の期待値を求めよ
解法
「 に含まれる白い頂点の個数の期待値」は、切り口を変えて「自分が『 に含まれる白い頂点』となる確率」を全頂点について求め、合計したものに等しくなる。 和の期待値=期待値の和。
条件
どのような頂点が「 に含まれる白い頂点」となるか?
- 自身が白いこと
- 子の部分木のうち、黒い頂点が1つでも含まれるものが、2つ以上あること
2番目の条件については、自身が に含まれるための条件となる。
子1 自 子3
○ -- ○ -- ○ -- ● -- ○ -- ●
|
子2○ -- ○
子1~3の部分木のうち、黒い頂点を含むものが1個しか無い場合、 はその部分木の中だけで完結してしまうので、自身は に含まれない。
一方、2個以上あれば、その黒い頂点同士を結ぶ過程で必ず自身も に含まれる。
確率
次に、その時の確率を考える。
長いので、説明上「黒い頂点が1つでも含まれる部分木」を「」と表す。
「子の部分木の内、 が2つ以上含まれる確率」は、余事象「 が1つ以下しかない確率」の方が計算しやすいので、これを1から引くことで求める。
ある 個の頂点について、そいつらが全て白である確率は 、 1つでも黒が含まれる確率は となる。
よって、たとえば子1~3の頂点数が 個なら、以下の合計となる。
- が0個…
- が1個
- 子1が …
- 子2が …
- 子3が …
この合計は、以下のように変形できる。
これを計算して余事象をとり、自身が白である確率1/2をかけたものが、求めるべき「自分が『 に含まれる白い頂点』となる確率」である。
全方位DP
ある頂点について確率を求めるには、自身から伸びる全方向の部分木の頂点数が必要となる。
しかし、たとえばある頂点を根として探索したとして、1回の探索だけでは「親方向の部分木の情報」がわからない。
根(仮)
/ \ ←こっち方面の頂点数はわからない
○ ●
/ /\ ←DFSなどで、各子の部分木の頂点数は求められる
??? ○○
|
○
そこで、1回まずDFSで子方向の部分木の情報だけ計算しておいて、2回目で親方向の部分木の情報を与えながら探索すると可能となる。
必要なのは頂点数だけなので、全体 から自分+子の頂点数の総計を引けば求まるね。
実装の方法
今回の制約では再帰数が 近くまであり得るが、DFSを再帰関数で書くと、ほぼ直線のような木の場合にTLEする。 スタックに積んでいく実装では間に合った。
「子の探索が終了した後、それを合計するなど集約して、自身の情報とする」ような探索の場合、再帰の方が書きやすいのだが、なかなか無視できない差が生じる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import sysdef 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_countdef 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 ansn = 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 = 0MOD = 10 ** 9 + 7d2 = 500000004 # 2^-1 mod 10**9+7d2s = [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) |

