−目次
AtCoder Beginner Contest 149 D~F問題メモ
D - Prediction and Restriction
問題
- じゃんけんマシンと N 回対戦する
- じゃんけんマシンの各回で出す手は決まっていて、文字列 S で与えられる
- グーで勝つと R 点、チョキで勝つと S 点、パーで勝つと P 点もらえる。あいこと負けは0点
- 各対戦で、K 回前に出した手と同じ手を出すことは禁止されている。最初の K 回は好きに出してよい
- 得点を最大化せよ
- 2≤N≤105
解法
貪欲に勝てる手を出していく。
K 回前の禁止ルールに引っかかり勝てる手を出せない場合のみ、仕方ないので違う手を出す。
この時、2通りの出し方があるので、さらに K 回後の勝利を邪魔しないような手を選ぶことができる。
K回前,今,K回後が同じ場合 |<-- K回 --|-- K回 -->| マシン パ パ パ 挑戦者 チ グorパ チ ↑ K回前と異なる2つの手のどちらを出してもK回後に勝てる手を出せる K回前,今が同じで、K回後が異なる場合 |<-- K回 --|-- K回 -->| マシン パ パ チ 挑戦者 チ パ グ ↑ K回前と異なり、かつK回後に勝つための手も邪魔しない手を選べる
- じゃんけんマシンの K 回前が違う手の場合、勝てる手の得点を加算
- じゃんけんマシンの K 回前が同じ手で、その時に得点を得ている場合は、禁止ルールで0点
- じゃんけんマシンの K 回前が同じ手で、その時に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 ] * 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 の数列 A1,A2,...,AN が与えられる
- ここから2要素の組 (Ai,Aj)(i=j でもよい、順番も区別する)の選び方は N2 通りある
- 2要素の和が大きい方から順に M 組を取った時、M 組の和の合計を求めよ
- 1≤N≤105
- 1≤M≤N2
- 1≤Ai≤105
解法
実際に全組の和を作ると N2 個できてえらいことになるので、何かしらまとめることを考える。
「和が X 以上になる組が M 個以上できるか?」を X を固定して考えると、 これは X が小さいほど少なく大きいほど多くなるので、二分探索でちょうど M 個以上作れる境界を探せる。
境界が分かれば、そのような組だけ抽出して和の合計を計算することができる。
X を探す範囲は 2~Ai の最大値x2、1回の判定が O(NlogN)、最後の和の合計の算出も O(NlogN) より、全部で O(NlogNlogAmax) でできる。
(例) 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 の組を採用すればよい。
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_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 頂点の木があり、構造も与えられる
- 各頂点を 12 ずつの確率で白か黒に塗る
- 黒く塗られた頂点を全て含むような最小の全域木を S とする
- S の中に含まれる白い頂点の個数の期待値を求めよ
- 2≤N≤2×105
解法
「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 個の頂点について、そいつらが全て白である確率は 12k、 1つでも黒が含まれる確率は 1−12k となる。
よって、たとえば子1~3の頂点数が k1,k2,k3 個なら、以下の合計となる。
- B が0個…12k1+k2+k3
- B が1個
- 子1が B … (1−12k1)12k2+k3
- 子2が B … (1−12k2)12k1+k3
- 子3が B … (1−12k3)12k1+k2
この合計は、以下のように変形できる。
- 12k2+k3+12k1+k3+12k1+k2−(3−1)12k1+k2+k3
これを計算して余事象をとり、自身が白である確率1/2をかけたものが、求めるべき「自分が『S に含まれる白い頂点』となる確率」である。
全方位DP
ある頂点について確率を求めるには、自身から伸びる全方向の部分木の頂点数が必要となる。
しかし、たとえばある頂点を根として探索したとして、1回の探索だけでは「親方向の部分木の情報」がわからない。
根(仮) / \ ←こっち方面の頂点数はわからない ○ ● / /\ ←DFSなどで、各子の部分木の頂点数は求められる ??? ○○ | ○
そこで、1回まずDFSで子方向の部分木の情報だけ計算しておいて、2回目で親方向の部分木の情報を与えながら探索すると可能となる。
必要なのは頂点数だけなので、全体 N から自分+子の頂点数の総計を引けば求まるね。
実装の方法
今回の制約では再帰数が 2×105 近くまであり得るが、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 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) |