ABC 080
Atcoder Beginner Contest 080
C - Shopping Street
問題
- 自店の営業時間を決める
- 月~金、AM・PM の10区分の時間帯がある
- 同じ商店街の他の N 個の店の営業時間は既に決まっている
- 「自店と店 i が共通して営業している時間帯の数 c」によって、自店が得られる利益 Pi,c が決まっている
- 自店の総利益は、Pi,c の全他店 i=1..N の和
- 総利益を最大化せよ
解法
210=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')
1 2 3 4 5 6 7 8 9 10 |
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で録画が終わる録画機が複数個ある
- チャンネルc1,c2を録画中とする
- 次にちょうど時刻tからチャンネルc2で始まる番組があった場合
- 本来は、c2を録画中の録画機で続けて録画できるため、増やさなくてよい
- 優先度キューから先に出されるのはc1を録画中の録画機
- 録画不可と判断して、録画機を1つ増やしてしまう
なので、以下の応急的な事前処理を施して、AC
- 同じチャンネルで連続している番組は、繋げて1番組とする
ソートを2回もしているため、効率悪い。もっと良い解法がある気がする。
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 |
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
時間の範囲がたかだか105なので、imos法が使えるようだ。
- 105+1のサイズの配列 A を作る
- 同じチャンネルの連続した番組はまとめる
- 番組の開始・終了時刻を S,T とすると、
- A[S−1]に1加算
- A[T]から1減算
- 全番組を処理後、A の累積和をとり、その中の最大値が答え
加算と減算するAのインデックスにちょっと迷ったが、時刻 T が含まれない時は A[T] でよい。
こちらの方が少し速い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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))) |