ARC 086
C - Not so Diverse
問題
- $N$ 個のボール、各ボールには数字 $a_i$ が書かれている
- いくつかの数字を書き換えることで、数字の種類を $K$ 種類以下にする
- 書き換える数を最小化
解法
逆に書き換えない数字を最大化することを考える。
各数字毎に、書かれたボールの数を数えて、多い方から $K$ 番目までの数が書かれたボールは書き換えずに残しておくと、最大となる。
10 2
1 1 1 2 2 3 4 4 4 5
数字毎にボールの数を数える {1:3, 2:2, 3:1, 4:3, 5:1}
ボール数が多い順にソート [(1,3), (4,3), (2,2), (3,1), (5,1)]
多い方からK=2番目までの数字は 1 と 4
それらが書かれたボールの数は計 6 個で、これをこのまま書き換えないのが最善
残りの4個を書き換えればよい
from collections import Counter
from operator import itemgetter
n, k = map(int, input().split())
a = Counter(map(int, input().split()))
sa = sorted(a.items(), key=itemgetter(1), reverse=True)
if len(sa) <= k:
print(0)
else:
print(sum(c for m, c in sa[k:]))
D - Non-decreasing
問題
- $N$ 要素の数列 $a={a_1,a_2,...,a_N}$
- 「任意の数 $x,y(1 \le x,y \le N)$ を選んで、$a_y$ に $a_x$ を加算」という操作を $2N$ 回まで行える
- 操作の結果、$a$ を単調増加列 $(a_1 \le a_2 \le ... \le a_N)$ としたい
- 操作手順の一例を示せ
解法
$a$ が全て非負なら、累積和を取ると条件を満たす($N-1$ 回の操作)
$a$ が全て非正なら、後ろから累積和を取ると条件を満たす($N-1$ 回の操作)
$a$ が正負混在なら、最大値(=正)と最小値(=負)、どちらの絶対値が大きいか調べ、最大値が大きければ負の要素に最大値を、最小値が大きければ正の数に最小値を足すと、全て非負または非正にできる(最大$N-1$ 回の操作)
反省
考えれば当たり前なんだけど、なんか累積和がパッと思いつかなかったなあ……
from itertools import starmap
def solve(n, a):
max_a, min_a = float('-inf'), float('inf')
max_i, min_i = 0, 0
for i, b in enumerate(a):
if b > max_a:
max_i, max_a = i + 1, b
if b < min_a:
min_i, min_a = i + 1, b
if max_a >= 0 and min_a >= 0:
return [(i, i + 1) for i in range(1, n)]
if max_a <= 0 and min_a <= 0:
return [(i, i - 1) for i in range(n, 1, -1)]
buf = []
if max_a >= -min_a:
for i, b in enumerate(a):
if b < 0:
buf.append((max_i, i + 1))
buf.extend((i, i + 1) for i in range(1, n))
else:
for i, b in enumerate(a):
if b > 0:
buf.append((min_i, i + 1))
buf.extend((i, i - 1) for i in range(n, 1, -1))
return buf
n = int(input())
a = list(map(int, input().split()))
buf = solve(n, a)
print(len(buf))
print(''.join(starmap('{} {}\n'.format, buf)))
E - Smuggling Marbles
問題
上記リンク参照
解法
根0からの深さが等しいものしか干渉しないので、まずはノードを深さごとに仕分けする。
(※干渉=初期状態でノード $n_1$ と $n_2$ に置かれたボールが、どこかのタイミングで1つのノードに同時に存在し取り除かれる可能性がある場合、$n_1$ と $n_2$ は干渉するということにする)
また、任意の部分木を考えると、その部分木の中では、外のボールが干渉することは無い。小さな部分木から解いていって、親に遷移するときに兄弟とマージしていく動的計画法が考えられる。
dp[n][i][k] = i 回の操作後に、ノード n に上ってくるボールが k 個になる場合の数(同階層内のみでの場合の数、他の階層を考慮しない)
ただし i = 0は初期状態を示す。また k = 0,1,2以上 の3通りのみ保持。場合の数において他の階層を考慮しない理由は、最後にまとめて計算できるので、途中で逐一やる必要が無いから。
このdpを、階層が深い位置にある $n$ から埋めていく。
最終的に $\sum_{i=0}dp[0][i][1]$ が答え…ではなく、これは違う階層に属するノードの初期状態の組み合わせを考慮できてないので、その組み合わせの数をかける必要がある。
つまり、第 $i$ 階層に属さないノードの数を$m_i$とすると、$\sum_{i=0}(dp[0][i][1] \times 2^{m_i})$ が答えとなる。
0 /\ 1 4 /\ \ 2 3 5
dpのやり方: 子が無いノードについて
まず、子が無いノードは、自身に初期状態でボールが置かれてるか置かれてないかしかない。i = 0 のみであり、その時の場合の数[0個,1個,2個以上] = [1,1,0] となる。
よって、ノード 2,3,5について、dp[2] = dp[3] = dp[5] = [[1,1,0]] となる。
dpのやり方: 子があるノードについて
まずi=0は、子が無いノードと同様[1,1,0]である。
i=1以降、子からのボールを統合することになる。2つずつマージできる。
ノード $v$ の子を $c_1,c_2,...$ とし
$$ dp[c_1][i] = [a_0,a_1,a_2] \\ dp[c_2][i] = [b_0,b_1,b_2] $$
であったとき、$c_1$ と $c_2$ の統合結果は、以下になる。(※処理1)
便宜的に、統合の途中結果を $dp[c_{tmp}]$ と表す。(実際にdpへの登録を行うわけでは無く、あくまで $dp[c_{tmp}]$ のデータ構造や意味が $dp[c_1]$ などと同じであることを示す)
\begin{eqnarray} dp[c_{tmp}][i][0] &=& a_0 b_0 \\ dp[c_{tmp}][i][1] &=& a_1 b_0 + a_0 b_1 \\ dp[c_{tmp}][i][2] &=& a_2(b_0 + b_1 + b_2) + a_1(b_1 + b_2) + a_0 b_2 \end{eqnarray}
数式で書くとわかりにくいが、要は、以下のような場合の数の計算をしているだけ
- (統合結果が1) = (c1から1 ∩ c2から0) ∪ (c1から0 ∩ c2から1)
- (統合結果が1の場合の数) = (c1が1の場合の数 × c2が0の場合の数) + (c1が0の場合の数 × c2が1の場合の数)
2つの子の深さが異なるとき、例えば $c_2$ より $c_1$ の方が部分木が深く、$dp[c_1][i]$ に対応する $dp[c_2][i]$ が無い、という時は、それ以降の $i$ については $dp[c_1][i]$ のままでよい
順次 $c_{tmp}$ にマージしていき、全ての子について統合が完了すると、次は「同じノードに2個以上同時にボールがあると取り除く」という操作に基づき、各 $i$ につき、以下を行う。(※処理2)
\begin{eqnarray} dp[c_{tmp}][i][0] &+=& dp[c_{tmp}][i][2] \\ dp[c_{tmp}][i][2] &=& 0 \end{eqnarray}
この処理の後、$dp[c_{tmp}]$ の先頭に[1,1,0]を付け加えたものが、$dp[v]$ となる。
ただ、これをそのまま実装すると、部分点は取れるが全ては時間的に間に合わないので、不要な処理をカットする。
子の数が1つの時
マージする必要は無く、$dp[v]$ は $dp[c]$ の先頭に[1,1,0]を加えればよい
子の数が2つ以上の時
最も深い子の深さ(dp[c]の長さ)を $d_1$, 2番目に深い子の深さを $d_2$ とすると、上記の※処理2(統合後、2個以上の場合の数を0個に移す処理)は、$d_2 \le i \lt d_1$の範囲については、行う必要は無い。
dpに登録するときは必ず※処理2によって「2個以上」の場合の数を 0 にしているので、更新が無かった部分については繰り返し行う必要が無い。
これらは、特に階層の深い入力ケースに対する枝刈りとして有効。
データ構造について
子を統合するとき、新しく配列を作るのでは無く「dp[c]の中で最長のもの」に対して統合するようにすると、余計なメモリを消費せずに済み、処理時間も速くなる。
その際、[1,1,0]を先頭に付け加える処理を頻繁に行うので、各 dp[n] は双方向リストで持つとよい。
仮に全てのdp[n]を保存していた場合、$N \le 2 \times 10^5$ で、全てが直列に繋がっているような意地悪なケースだと、dp[n]のサイズは下からの階層に比例するので上は $N$ に近くなり、それが $N$ 個あり、それぞれが3つの状態を記録するので、$3N(N-1)/2 = 6 \times 10^{10}$ 個のintをもつことになり、MLEの可能性もある。
処理の終わった階層の情報が正しくなくなるが、問題を解くには使わないので問題ないことにする。
from collections import deque
def get_pow():
cache = {}
def func(x):
if x not in cache:
cache[x] = pow(2, x, mod)
return cache[x]
return func
mod = 1000000007
n = int(input())
parents = list(map(int, input().split()))
children = [set() for _ in range(n + 1)]
for c, p in enumerate(parents):
children[p].add(c + 1)
levels = [{0}]
while True:
level = set()
for p in levels[-1]:
level.update(children[p])
if not level:
break
levels.append(level)
levels.reverse()
level_node_count = []
balls = [None] * (n + 1)
for i, level in enumerate(levels):
level_node_count.append(len(level))
for node in level:
cn = children[node]
if cn:
if len(cn) == 1:
bs = balls[next(iter(cn))]
bs.appendleft([1, 1, 0])
balls[node] = bs
continue
balls_from_children = [balls[c] for c in children[node]]
balls_from_children.sort(key=len)
bs1 = balls_from_children[0]
for bs2 in balls_from_children[1:]:
for (b10, b11, b12), b2 in zip(bs1, bs2):
b2[2] = ((b11 + b12) * b2[1] + b12 * b2[0]) % mod
b2[1] = (b10 * b2[1] + b11 * b2[0]) % mod
b2[0] = b2[0] * b10 % mod
bs1 = bs2
lim = len(balls_from_children[-2])
for i, b in enumerate(bs1):
if i >= lim:
break
b[0] = (b[0] + b[2]) % mod
b[2] = 0
bs1.appendleft([1, 1, 0])
balls[node] = bs1
else:
balls[node] = deque([[1, 1, 0]])
level_node_count.reverse()
pow2 = get_pow()
print(sum(b[1] * pow2(n - l + 1) % mod for l, b in zip(level_node_count, balls[0])) % mod)

