Processing math: 100%
[[ARC 086]]

ARC 086

C - Not so Diverse

問題

  • N 個のボール、各ボールには数字 ai が書かれている
  • いくつかの数字を書き換えることで、数字の種類を 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個を書き換えればよい

1
2
3
4
5
6
7
8
9
10
11
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=a1,a2,...,aN
  • 「任意の数 x,y(1x,yN) を選んで、ayax を加算」という操作を 2N 回まで行える
  • 操作の結果、a を単調増加列 (a1a2...aN) としたい
  • 操作手順の一例を示せ

解法

a が全て非負なら、累積和を取ると条件を満たす(N1 回の操作)

a が全て非正なら、後ろから累積和を取ると条件を満たす(N1 回の操作)

a が正負混在なら、最大値(=正)と最小値(=負)、どちらの絶対値が大きいか調べ、最大値が大きければ負の要素に最大値を、最小値が大きければ正の数に最小値を足すと、全て非負または非正にできる(最大N1 回の操作)

反省

考えれば当たり前なんだけど、なんか累積和がパッと思いつかなかったなあ……

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
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からの深さが等しいものしか干渉しないので、まずはノードを深さごとに仕分けする。

(※干渉=初期状態でノード n1n2 に置かれたボールが、どこかのタイミングで1つのノードに同時に存在し取り除かれる可能性がある場合、n1n2 は干渉するということにする)

また、任意の部分木を考えると、その部分木の中では、外のボールが干渉することは無い。小さな部分木から解いていって、親に遷移するときに兄弟とマージしていく動的計画法が考えられる。

dp[n][i][k] = i 回の操作後に、ノード n に上ってくるボールが k 個になる場合の数(同階層内のみでの場合の数、他の階層を考慮しない)

ただし i = 0は初期状態を示す。また k = 0,1,2以上 の3通りのみ保持。場合の数において他の階層を考慮しない理由は、最後にまとめて計算できるので、途中で逐一やる必要が無いから。

このdpを、階層が深い位置にある n から埋めていく。

最終的に i=0dp[0][i][1] が答え…ではなく、これは違う階層に属するノードの初期状態の組み合わせを考慮できてないので、その組み合わせの数をかける必要がある。

つまり、第 i 階層に属さないノードの数をmiとすると、i=0(dp[0][i][1]×2mi) が答えとなる。

    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 の子を c1,c2,... とし

dp[c1][i]=[a0,a1,a2]dp[c2][i]=[b0,b1,b2]

であったとき、c1c2 の統合結果は、以下になる。(※処理1)

便宜的に、統合の途中結果を dp[ctmp] と表す。(実際にdpへの登録を行うわけでは無く、あくまで dp[ctmp] のデータ構造や意味が dp[c1] などと同じであることを示す)

dp[ctmp][i][0]=a0b0dp[ctmp][i][1]=a1b0+a0b1dp[ctmp][i][2]=a2(b0+b1+b2)+a1(b1+b2)+a0b2

数式で書くとわかりにくいが、要は、以下のような場合の数の計算をしているだけ

  • (統合結果が1) = (c1から1 ∩ c2から0) ∪ (c1から0 ∩ c2から1)
  • (統合結果が1の場合の数) = (c1が1の場合の数 × c2が0の場合の数) + (c1が0の場合の数 × c2が1の場合の数)

2つの子の深さが異なるとき、例えば c2 より c1 の方が部分木が深く、dp[c1][i] に対応する dp[c2][i] が無い、という時は、それ以降の i については dp[c1][i] のままでよい

順次 ctmp にマージしていき、全ての子について統合が完了すると、次は「同じノードに2個以上同時にボールがあると取り除く」という操作に基づき、各 i につき、以下を行う。(※処理2)

dp[ctmp][i][0]+=dp[ctmp][i][2]dp[ctmp][i][2]=0

この処理の後、dp[ctmp] の先頭に[1,1,0]を付け加えたものが、dp[v] となる。

ただ、これをそのまま実装すると、部分点は取れるが全ては時間的に間に合わないので、不要な処理をカットする。

子の数が1つの時

マージする必要は無く、dp[v]dp[c] の先頭に[1,1,0]を加えればよい

子の数が2つ以上の時

最も深い子の深さ(dp[c]の長さ)を d1, 2番目に深い子の深さを d2 とすると、上記の※処理2(統合後、2個以上の場合の数を0個に移す処理)は、d2i<d1の範囲については、行う必要は無い。

dpに登録するときは必ず※処理2によって「2個以上」の場合の数を 0 にしているので、更新が無かった部分については繰り返し行う必要が無い。

これらは、特に階層の深い入力ケースに対する枝刈りとして有効。

データ構造について

子を統合するとき、新しく配列を作るのでは無く「dp[c]の中で最長のもの」に対して統合するようにすると、余計なメモリを消費せずに済み、処理時間も速くなる。

その際、[1,1,0]を先頭に付け加える処理を頻繁に行うので、各 dp[n] は双方向リストで持つとよい。

仮に全てのdp[n]を保存していた場合、N2×105 で、全てが直列に繋がっているような意地悪なケースだと、dp[n]のサイズは下からの階層に比例するので上は N に近くなり、それが N 個あり、それぞれが3つの状態を記録するので、3N(N1)/2=6×1010 個のintをもつことになり、MLEの可能性もある。

処理の終わった階層の情報が正しくなくなるが、問題を解くには使わないので問題ないことにする。

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
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)

programming_algorithm/contest_history/atcoder/2017/1210_arc086.txt · 最終更新: 2018/07/25 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0