ABC 080
Atcoder Beginner Contest 080
C - Shopping Street
問題
- 自店の営業時間を決める
- 月~金、AM・PM の10区分の時間帯がある
- 同じ商店街の他の $N$ 個の店の営業時間は既に決まっている
- 「自店と店 $i$ が共通して営業している時間帯の数 $c$」によって、自店が得られる利益 $P_{i,c}$ が決まっている
- 自店の総利益は、$P_{i,c}$ の全他店 $i=1..N$ の和
- 総利益を最大化せよ
解法
$2^{10}=1024$通りなので、全ての営業時間の組み合わせを全探索。店の数も100以下なので、間に合う。
営業時間を10桁の2進数で表すと、「自店と他店の共通して営業している時間帯の数」は、「自店の営業時間を表す数と、他店の営業時間を表す数の、AND(論理積)を取った後に立っているビット数」と表せる。
月AM | 月PM | 火AM | 火PM | 水AM | 水PM | 木AM | 木PM | 金AM | 金PM | 2進数表現 | 共通 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
自店 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1010110011 | |
他店 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0111011011 | |
AND | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0010010011 | 4 |
こうすると、自店の営業時間の全探索も「1~1023の数値の全探索」で表現でき、1回の計算も配列で持つよりは速い。
pythonでは、立っているビット数を直接得られる関数は無いが、2進数テキストに変換して'1'の数を数えるのが速いらしい。
bin(x).count('1')
from operator import mul n = int(input()) cof = [1 << x for x in range(10)] fs = [sum(map(mul, map(int, input().split()), cof)) for _ in range(n)] ps = [list(map(int, input().split())) for _ in range(n)] ans = float('-inf') for g in range(1, 1024): ans = max(ans, sum(p[bin(f & g).count('1')] for f, p in zip(fs, ps))) print(ans)
D - Recording
問題
- $N$ 個のテレビ番組を録画機で全て録画
- 録画機は、あるチャンネルの時刻 $S$ から時刻 $T$ までを録画するとき、時刻 $S-0.5$から、他のチャンネルの録画には使えない
- つまり、同じチャンネルなら番組の時刻が連続していても録画できるが、違うチャンネルなら時刻0.5以上間隔が無いと録画できない
- 録画機はいくつ必要?
解法
はじめに思いついた解法(WA)
- 番組を開始時刻順にソート
- 優先度キューを使って、早く終わる録画機から次の番組を録画
- 一番早く終わる録画機でも次の番組を録画できない場合、優先度キューに追加(キューの要素数が1増える)
- 最終的な優先度キューの要素数が、必要な録画機数
だが、これだと次のような場合を捉えきれない。
- 時刻$t$で録画が終わる録画機が複数個ある
- チャンネル$c_1, c_2$を録画中とする
- 次にちょうど時刻$t$からチャンネル$c_2$で始まる番組があった場合
- 本来は、$c_2$を録画中の録画機で続けて録画できるため、増やさなくてよい
- 優先度キューから先に出されるのは$c_1$を録画中の録画機
- 録画不可と判断して、録画機を1つ増やしてしまう
なので、以下の応急的な事前処理を施して、AC
- 同じチャンネルで連続している番組は、繋げて1番組とする
ソートを2回もしているため、効率悪い。もっと良い解法がある気がする。
from heapq import heapreplace, heappush n, v = map(int, input().split()) programs = [tuple(map(int, input().split())) for _ in range(n)] programs.sort() tmp = [None] * v combined = [] for s, t, c in programs: c -= 1 prev = tmp[c] if prev is not None: ps, pt, pc = prev if pt == s: tmp[c] = (ps, t, c) else: combined.append((ps, pt, pc)) tmp[c] = (s, t, c) else: tmp[c] = (s, t, c) for prev in tmp: if prev is not None: combined.append(prev) combined.sort() recorders = [(-1, None)] for s, t, c in combined: end, channel = recorders[0] if end < s or c == channel: heapreplace(recorders, (t, c)) else: heappush(recorders, (t, c)) print(len(recorders))
解法2
時間の範囲がたかだか$10^5$なので、imos法が使えるようだ。
- $10^5+1$のサイズの配列 $A$ を作る
- 同じチャンネルの連続した番組はまとめる
- 番組の開始・終了時刻を $S,T$ とすると、
- $A[S-1]$に1加算
- $A[T]$から1減算
- 全番組を処理後、$A$ の累積和をとり、その中の最大値が答え
加算と減算する$A$のインデックスにちょっと迷ったが、時刻 $T$ が含まれない時は $A[T]$ でよい。
こちらの方が少し速い。
from collections import defaultdict from itertools import accumulate n, v = map(int, input().split()) programs = defaultdict(list) for _ in range(n): s, t, c = map(int, input().split()) programs[c].append((s, t)) imos = [0] * (10 ** 5 + 1) for c, p in programs.items(): p.sort() ps, pt = p[0] for s, t in p[1:]: if pt == s: pt = t else: imos[ps - 1] += 1 imos[pt] -= 1 ps, pt = s, t imos[ps - 1] += 1 imos[pt] -= 1 print(max(accumulate(imos)))