SlideShare a Scribd company logo
하스켈로 알고리즘 문제 풀기 2
Codeforces Round 364 (Div 2)
Codeforces Round 364 (Div 2)
�� http://codeforces.com/contest/701
• 처음 다섯 문제 분석
• A. Cards
• B. Cells Not Under Attack
• C. They Are Everywhere
• D. As Fast As Possible
• E. Connecting Universities
• 문제의 정확한 서술과 입출력 예시는 원문 참조
A. Cards
• http://codeforces.com/contest/701/problem/A
• 키워드: 리스트 모나드
• 문제: 숫자가 적힌 카드 n개를 n/2명에게 2개씩 배분한다.
각 플레이어가 가진 패의 합이 같도록 하라.
• 카드 개수 2 ≤ 𝑛 ≤ 100
• 카드에 적힌 수 1 ≤ 𝑎𝑖 ≤ 100 (1 ≤ 𝑖 ≤ 𝑛)
• 답이 있음이 보장된다
• 답이 여러 개일 경우 아무거나 출력
A. Cards
• 입력: n이 작기 때문에 그냥 String과 리스트 사용
main = do
n <- read `fmap` getLine :: IO Int
ns <- (map read . words) `fmap` getLine :: IO [Int]
• 한 플레이어의 두 카드의 합 * 플레이어 수 = 모든 카드의 합
let goal = sum ns `div` (n `div` 2)
• 모든 경우의 수 생성, 합이 같은 경우만 필터링, 처음 해 출력
let picks = pick2 ns n goal
let soln = head (filter same picks)
printSoln soln
• 리스트 모나드 이용
• 정석 풀이는 카드를 정렬해서 양 끝부터 짝짓는 것
• http://codeforces.com/blog/entry/46283
import Data.List
import Control.Monad
getInt = read `fmap` getLine :: IO Int
main = do
n <- getInt
ns <- (map read . words) `fmap` getLine :: IO [Int]
let goal = sum ns `div` (n `div` 2)
let picks = pick2 ns n goal
let soln = head (filter same picks)
printSoln soln
pick2 ns n goal = f (zip [1..n] ns) n where
f ns 0 = return []
f ns n = do
(i,x) <- ns
let ns' = delete (i,x) ns
(j,y) <- ns'
let ns'' = delete (j,y) ns'
guard $ (x+y) == goal
rest <- f ns'' (n-2)
return $ (i,x):(j,y):rest
same ((i,x):(j,y):ns) = f (x+y) ns where
f z [] = True
f z ((i,x):(j,y):ns)
| z == x + y = f z ns
| otherwise = False
printSoln [] = return ()
printSoln ((i,x):(j,y):ns) = do
putStrLn $ show i ++ " " ++ show j
printSoln ns
A. Cards
B. Cells Not Under Attack
• http://codeforces.com/contest/701/problem/B
• 키워드: IntSet, Int64
• 문제: n x n 체스판에 룩 m개를 겹치지 않게 차례대로 놓는다.
룩을 놓을 때마다 어떤 룩에게서도 안전한 칸의 개수를 출력하라.
• 체스판 크기 1 ≤ 𝑛 ≤ 100000
• 룩 개수 1 ≤ 𝑚 ≤ min(100000, 𝑛2
)
B. Cells Not Under Attack
• 룩을 (x, y)에 놓으면 x열 또는 y행의 모든 칸은 안전하지 않다
• 룩이 하나라도 놓인 열이 xs 개, 행이 ys 개라 하면
• 안전한 칸은 (n-xs) * (n-ys) 개
• 체스판의 크기는 최대 1000002
= 1010
> 233
이므로 64비트 정수 필요
• 내 컴퓨터에서는 Int가 64비트였으나 채점기에서는 32비트
• 콘테스트 당시 왜 오답인지 몰라서 통과 실패
• 이미 룩이 있는 열 또는 행을 저장할 자료구조
• 리스트를 쓰면 시간 초과 (쓰기는 O(1), 검색은 O(N)) -> 최종 시간복잡도 O(𝑁2
)
• 배열(Data.Array)은 못 쓴다 (쓰기가 O(N). 그리고 MArray는 코드포스에서 막아놓음)
• IntSet 활용
• Set과 Map은 쓰기 가능한 배열이 필요한 알고리즘을 하스켈로 구현할 때 적절한 대안
B. Cells Not Under Attack
import Data.List hiding (insert)
import Data.Int
import Data.IntSet hiding (map)
main = do
[n,m] <- (map read . words) `fmap` getLine :: IO [Int]
rocks <- (map (map read . words) . take m . lines) `fmap` getContents :: IO [[Int]]
let n64 = fromIntegral n :: Int64
solve rocks empty empty n64 n64
solve [] cols rows emptyCols emptyRows = return ()
solve ([x,y]:rocks) cols rows emptyCols emptyRows = do
let col_already = member x cols
let row_already = member y rows
let emptyCols' = if col_already then emptyCols else emptyCols - 1
let emptyRows' = if row_already then emptyRows else emptyRows - 1
putStr $ show $ emptyCols' * emptyRows'
putChar ' '
let cols' = insert x cols
let rows' = insert y rows
solve rocks cols' rows' emptyCols' emptyRows'
C. They Are Everywhere
• http://codeforces.com/contest/701/problem/C
• 키워드: Data.Map, 그리디 알고리즘
• 문제: 방이 일렬로 n개 있고 각각 바로 왼쪽, 오른쪽 방과 연결된 집이 있다.
각 방에는 포켓몬이 하나 있다. (포켓몬은 글자 a~z, A~Z 중 하나로 표현)
한 방에서 출발해 모든 종류를 잡을 때까지 오른쪽 방으로 건너간다.
방문해야 하는 방의 최소 개수를 구하여라.
• 방의 개수 1 ≤ 𝑛 ≤ 100000
• 해가 있음이 보장된다
C. They Are Everywhere
• 1번 방에서 시작해 모든 종류를 잡을 때까지 전진
• 현재 해: (1, 𝑒1)
• 지나온 방의 개수 𝑥1 계산
• 각 방을 방문하며 잡은 포켓몬 기록
• 1번 방에서 잡은 포켓몬을 기록에서 제거
• 𝑒1번 방에서 시작해 모든 종류를 잡을 때까지 전진
• 지나온 방의 개수 𝑥2 계산
• 최적해 = min (𝑥1, 𝑥2) 갱신
• 2번 방에서 잡은 포켓몬을 기록에서 제거
• 𝑒2번 방에서 시작해 모든 종류를 잡을 때까지 전진
• …
• m번 방에서 잡은 포켓몬을 기록에서 제거
• 더 이상 모든 종류를 잡을 수 없으면 종료
C. They Are Everywhere
• Data.Set은 어떤 값이 집합에 소속되는지만 검사 가능
• Data.Map을 이용해서 키(포켓몬 종류)를 값(그 종류를 잡은 개수)으로 매핑
import Data.List
import Data.Array
import Data.Maybe
import qualified Data.Map.Strict as M
main = do
n <- read `fmap` getLine :: IO Int
flats <- getLine
let flatAry = listArray (1, length flats) flats
let numPoke = (length . group . sort) flats
print $ solve numPoke flatAry
type Book = M.Map Char Int
type Solution = (Book, Int, Int) -- (currSet, beginIx, endIx)
solve numPoke flats = f (M.empty, 1, 1) 100000 where
f :: Solution -> Int -> Int
f curr best = case walk numPoke flats curr of
Nothing -> best
Just next -> f (removeHead next flats) (min best (cost next))
cost (_, b, e) = e - b
removeHead (book, b, e) flats = (book', b+1, e) where
x = flats ! b
book' = if book M.! x == 1
then M.delete x book
else M.adjust (y->y-1) x book
walk :: Int -> Array Int Char -> Solution -> Maybe Solution
walk num flats (book, b, e) = f book e where
(ary1, aryN) = bounds flats
len = aryN - ary1 + 1
f book e
| e > len && M.size book >= num = Just (book, b, e)
| e > len && M.size book < num = Nothing
| M.size book == num = Just (book, b, e)
| otherwise = f (M.insertWith (+) (flats ! e) 1 book) (e + 1)
C. They Are Everywhere
D. As Fast As Possible
• http://codeforces.com/contest/701/problem/D
• 키워드: ad hoc, 이진 탐색(binary search)
• 문제: 일직선 도로의 한쪽 끝에 사람 n명, k명을 태울 수 있는 버스가 있다.
각자 버스를 최대 한 번만 탈 수 있다. 반대편 끝으로 도달하는 최소 시간을 구하여라.
• 버스 탑승, 하차, 버스가 방향을 돌리는 것은 시간이 걸리지 않는다.
• 사람 수 1 ≤ 𝑛 ≤ 100000
• 버스의 수용량 1 ≤ 𝑘 ≤ 𝑛
• 사람과 버스의 속도 1 ≤ 𝑣1 < 𝑣2 ≤ 109
• 도로 길이 1 ≤ 𝑙 ≤ 109
D. As Fast As Possible
• 이진 탐색을 이용한 해법: http://pakapa104.hatenablog.com/entry/2016/07/25/135255
• 하지만 식을 계산해서 O(1) 해답 도출 가능
• 사람들을 𝑚 =
𝑛
𝑘
개 그룹으로 분할한다.
• 모든 그룹은 버스를 타는 시간이 x로 동일하다.
• 걷는 것보다 버스를 타는 것이 무조건 빠르다
• 버스를 한 그룹이 x + e, 다른 그룹이 x – e 시간 탄다고 가정 (e > 0)
• x - e 시간 타는 그룹은 e 시간만큼 더 걷게 된다 -> 더 오래 걸린다
• 따라서 모든 그룹은 버스를 타는 시간이 같아야 한다
D. As Fast As Possible
• 버스가 한 그룹을 태우고 갔다가 나머지 그룹에게 돌아오는 시간 y
• 버스 전진: 𝑥𝑣2
• 보행자 전진: 𝑥𝑣1
• 거리 차이: 𝑥 𝑣2 − 𝑣1
• 버스 후진, 보행자 전진: y =
𝑥 𝑣2−𝑣1
(𝑣2+𝑣1)
• 버스를 타는 시간 x의 최대값은?
• 그룹 𝑔1, 𝑔2, … , 𝑔 𝑚
• 𝑔1은 버스를 x 시간 타고 남은 거리를 걸어간다
• 𝑔2은 x + y 시간을 걷고 버스를 x 시간 타고 남은 거리를 걸어간다
• 𝑔3은 2(x + y) 시간을 걷고 버스를 x 시간 타고 남은 거리를 걸어간다
• …
• 𝑔 𝑚 이 x + y m − 1 시간을 걷고 남은 거리는 버스를 타기에 충분히 길어야 한다 ( ≥ 𝑥𝑣2)
D. As Fast As Possible
• 𝑔 𝑚은 x + y m − 1 시간을 걷고 남은 거리는 버스를 탄다
• 𝑔 𝑚이 걸은 거리: 𝑥 + 𝑦 𝑚 − 1 𝑣1 = x 1 +
v2−𝑣1
𝑣2+𝑣1
𝑚 − 1 𝑣1 ≤ 𝐿 − 𝑥𝑣2
• 𝑥 ≤
𝐿
𝑣2 +𝑣1 1+
v2−𝑣1
𝑣2+𝑣1
(𝑚−1)
• x의 최대값은
𝐿
𝑣2 +𝑣1 1+
v2−𝑣1
𝑣2+𝑣1
(𝑚−1)
• 𝑔1, 𝑔2, … , 𝑔 𝑚−1은 먼저 도착
• 𝑔 𝑚의 총 이동 시간 𝑥 +
𝐿 −𝑥𝑣2
𝑣1
이 답
D. As Fast As Possible
main = do
ps@[n, l, v1, v2, k] <- (map read . words) `fmap` getLine :: IO [Int]
let ps' = map fromIntegral ps :: [Double]
print $ solve ps'
solve :: [Double] -> Double
solve [n, l, v1, v2, k] = t1 + x where
m = fromIntegral (ceiling (n / k)) :: Double
v3 = (v2 - v1) / (v2 + v1)
x = l / (v2 + (m-1)*(v1 * (1+ v3)))
t1 = (l - v2*x) / v1
E. Connecting Universities
• http://codeforces.com/contest/701/problem/E
• 키워드: 그래프, 스패닝 트리, 깊이 우선 탐색(DFS), ByteString
• 문제: 마을 n개가 간선 n-1개로 연결되어 스패닝 트리를 형성한다.
이 중 2k개 마을에 도시가 있고, 모든 간선의 가중치는 1이다.
도시들을 k개 짝으로 묶으려 하는데, 모든 짝의 거리의 합이 최대가 되도록 한다.
거리의 합의 최댓값을 구하여라.
• 마을 수 2 ≤ 𝑛 ≤ 200000
• 도시 수 1 ≤ 2𝑘 ≤ 𝑛
• 참고 자료
• http://kenkoooo.hatenablog.com/entry/2016/07/23/043406
• http://blog.csdn.net/baidu_19306071/article/details/52005557
E. Connecting Universities
• 두 마을 u, v를 연결하는 임의의 간선에 대해
• u쪽에 있는 도시가 x개, v쪽에 있는 도시가 2k - x개라 하면
• u쪽의 도시와 v쪽의 도시를 짝지어야 한다
• 가능한 짝은 min(x, 2k - x) 개
• 한 쪽의 두 도시를 짝지으면 무조건 거리의 총합이 낮아진다
vu
E. Connecting Universities
• u쪽의 도시와 v쪽의 도시끼리 짝지을 경우 비용 P
• 다음 두 비용의 합
• path(u1, u) + path(u, v) + path(v, v1)
• path(u2, u) + path(u, v) + path(v, v2)
• u쪽의 도시끼리, v쪽의 도시끼리 짝지을 경우 비용 Q
• Q = path(u1, u2) + path(v1, v2)
• Q <= path(u1,u) + path(u,u2) + path(v1, v) + path(v, v2) < P
vu
u1
u2
v1
v2
E. Connecting Universities
• 한 마을을 루트 삼아 순회 시작
• 마을마다 해당 마을을 포함해 DFS 트리상 그 아래에 있는 도시의 개수 x 계산
• 마을에 도시가 있으면 x = 1, 없으면 x = 0 으로 설정하고 자식 노드들의 x를 더한다
• 후위 순회 (postorder traversal) 필요
• 모든 마을의 min(x, 2k-x)를 더한 것이 답
• 한 간선에 저장된 값이 min(x, 2k-x) 인 것의 의미
• 이 간선을 지나는 경로가 min(x, 2k-x) 개
• 간선의 가중치(길이)는 1
• 모든 간선에 저장된 값을 더하면 모든 경로의 길이를 더한 것이 된다
E. Connecting Universities
• 입력이 너무 길어서 ByteString 사용 (String 쓰면 시간 초과)
• -- getInts = (map read . words) `fmap` getLine :: IO [Int]
• getInts = (map (fst . fromJust . BS8.readInt) . BS8.words) `fmap` BS.getLine :: IO [Int]
• 그래프는 인접 리스트로 표현
• type Tree = Map.Map Int (Int, [Int]) -- (data, children)
• 답이 Int의 범위를 넘어서 Integer 사용
• answer :: Tree -> Int -> Int -> Integer
• Integer는 하스켈에 내장된 big integer 타입
• 답이 얼마나 큰지 따지기 귀찮을 때 사용. 계산 속도는 Int보다 느리다
import Control.Monad
import Data.Array
import Data.Maybe
import qualified Data.Map.Strict as Map
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
getInt = read `fmap` getLine :: IO Int
--getInts = (map read . words) `fmap` getLine :: IO [Int]
getInts = (map (fst . fromJust . BS8.readInt) . BS8.words) `fmap` BS.getLine :: IO [Int]
type Tree = Map.Map Int (Int, [Int]) -- (data, children)
main = do
[n, k] <- getInts
cities <- getInts
edges <- replicateM (n-1) getInts
let tree0 = Map.fromList [(key, (0,[])) | key <- [1..n]] :: Tree
let tree = foldl addCity (foldl addEdge tree0 edges) cities
let t = dfs tree 1
print $ answer t n (k*2)
addEdge :: Tree -> [Int] -> Tree
addEdge g [u,v] = g'' where
g' = Map.update ((x,es) -> Just (x,u:es)) v g
g'' = Map.update ((x,es) -> Just (x,v:es)) u g'
addCity :: Tree -> Int -> Tree
addCity g i = Map.update ((_,es) -> Just (1,es)) i g
dfs :: Tree -> Int -> Tree
dfs tree start = f 0 tree start where
link curr prev = filter (/= prev) $ snd (tree Map.! curr)
f prev tree curr
| snd (tree Map.! curr) == [] = tree
| otherwise = let x = foldl (f curr) tree (link curr prev) in seq x (calc x curr prev)
calc tree curr prev = Map.update ((d,es) -> Just (d+cost,es)) curr tree where
cost = sum $ map (j -> fst (tree Map.! j)) (link curr prev) :: Int
answer :: Tree -> Int -> Int -> Integer
answer tree n k2 = sum $ map (i -> min (cnt i) (k2' - (cnt i))) [1..n] where
k2' = fromIntegral k2
cnt i = fromIntegral $ fst (tree Map.! i)
E. Connecting Universities
E. Connecting Universities
• 시간 제한: 3초
• 메모리 제한: 256MB
• 간신히 통과

More Related Content

하스켈로 알고리즘 문제 풀기 2

  • 1. 하스켈로 알고리즘 문제 풀기 2 Codeforces Round 364 (Div 2)
  • 2. Codeforces Round 364 (Div 2) • http://codeforces.com/contest/701 • 처음 다섯 문제 분석 • A. Cards • B. Cells Not Under Attack • C. They Are Everywhere • D. As Fast As Possible • E. Connecting Universities • 문제의 정확한 서술과 입출력 예시는 원문 참조
  • 3. A. Cards • http://codeforces.com/contest/701/problem/A • 키워드: 리스트 모나드 • 문제: 숫자가 적힌 카드 n개를 n/2명에게 2개씩 배분한다. 각 플레이어가 가진 패의 합이 같도록 하라. • 카드 개수 2 ≤ 𝑛 ≤ 100 • 카드에 적힌 수 1 ≤ 𝑎𝑖 ≤ 100 (1 ≤ 𝑖 ≤ 𝑛) • 답이 있음이 보장된다 • 답이 여러 개일 경우 아무거나 출력
  • 4. A. Cards • 입력: n이 작기 때문에 그냥 String과 리스트 사용 main = do n <- read `fmap` getLine :: IO Int ns <- (map read . words) `fmap` getLine :: IO [Int] • 한 플레이어의 두 카드의 합 * 플레이어 수 = 모든 카드의 합 let goal = sum ns `div` (n `div` 2) • 모든 경우의 수 생성, 합이 같은 경우만 필터링, 처음 해 출력 let picks = pick2 ns n goal let soln = head (filter same picks) printSoln soln • 리스트 모나드 이용 • 정석 풀이는 카드를 정렬해서 양 끝부터 짝짓는 것 • http://codeforces.com/blog/entry/46283
  • 5. import Data.List import Control.Monad getInt = read `fmap` getLine :: IO Int main = do n <- getInt ns <- (map read . words) `fmap` getLine :: IO [Int] let goal = sum ns `div` (n `div` 2) let picks = pick2 ns n goal let soln = head (filter same picks) printSoln soln pick2 ns n goal = f (zip [1..n] ns) n where f ns 0 = return [] f ns n = do (i,x) <- ns let ns' = delete (i,x) ns (j,y) <- ns' let ns'' = delete (j,y) ns' guard $ (x+y) == goal rest <- f ns'' (n-2) return $ (i,x):(j,y):rest same ((i,x):(j,y):ns) = f (x+y) ns where f z [] = True f z ((i,x):(j,y):ns) | z == x + y = f z ns | otherwise = False printSoln [] = return () printSoln ((i,x):(j,y):ns) = do putStrLn $ show i ++ " " ++ show j printSoln ns A. Cards
  • 6. B. Cells Not Under Attack • http://codeforces.com/contest/701/problem/B • 키워드: IntSet, Int64 • 문제: n x n 체스판에 룩 m개를 겹치지 않게 차례대로 놓는다. 룩을 놓을 때마다 어떤 룩에게서도 안전한 칸의 개수를 출력하라. • 체스판 크기 1 ≤ 𝑛 ≤ 100000 • 룩 개수 1 ≤ 𝑚 ≤ min(100000, 𝑛2 )
  • 7. B. Cells Not Under Attack • 룩을 (x, y)에 놓으면 x열 또는 y행의 모든 칸은 안전하지 않다 • 룩이 하나라도 놓인 열이 xs 개, 행이 ys 개라 하면 • 안전한 칸은 (n-xs) * (n-ys) 개 • 체스판의 크기는 최대 1000002 = 1010 > 233 이므로 64비트 정수 필요 • 내 컴퓨터에서는 Int가 64비트였으나 채점기에서는 32비트 • 콘테스트 당시 왜 오답인지 몰라서 통과 실패 • 이미 룩이 있는 열 또는 행을 저장할 자료구조 • 리스트를 쓰면 시간 초과 (쓰기는 O(1), 검색은 O(N)) -> 최종 시간복잡도 O(𝑁2 ) • 배열(Data.Array)은 못 쓴다 (쓰기가 O(N). 그리고 MArray는 코드포스에서 막아놓음) • IntSet 활용 • Set과 Map은 쓰기 가능한 배열이 필요한 알고리즘을 하스켈로 구현할 때 적절한 대안
  • 8. B. Cells Not Under Attack import Data.List hiding (insert) import Data.Int import Data.IntSet hiding (map) main = do [n,m] <- (map read . words) `fmap` getLine :: IO [Int] rocks <- (map (map read . words) . take m . lines) `fmap` getContents :: IO [[Int]] let n64 = fromIntegral n :: Int64 solve rocks empty empty n64 n64 solve [] cols rows emptyCols emptyRows = return () solve ([x,y]:rocks) cols rows emptyCols emptyRows = do let col_already = member x cols let row_already = member y rows let emptyCols' = if col_already then emptyCols else emptyCols - 1 let emptyRows' = if row_already then emptyRows else emptyRows - 1 putStr $ show $ emptyCols' * emptyRows' putChar ' ' let cols' = insert x cols let rows' = insert y rows solve rocks cols' rows' emptyCols' emptyRows'
  • 9. C. They Are Everywhere • http://codeforces.com/contest/701/problem/C • 키워드: Data.Map, 그리디 알고리즘 • 문제: 방이 일렬로 n개 있고 각각 바로 왼쪽, 오른쪽 방과 연결된 집이 있다. 각 방에는 포켓몬이 하나 있다. (포켓몬은 글자 a~z, A~Z 중 하나로 표현) 한 방에서 출발해 모든 종류를 잡을 때까지 오른쪽 방으로 건너간다. 방문해야 하는 방의 최소 개수를 구하여라. • 방의 개수 1 ≤ 𝑛 ≤ 100000 • 해가 있음이 보장된다
  • 10. C. They Are Everywhere • 1번 방에서 시작해 모든 종류를 잡을 때까지 전진 • 현재 해: (1, 𝑒1) • 지나온 방의 개수 𝑥1 계산 • 각 방을 방문하며 잡은 포켓몬 기록 • 1번 방에서 잡은 포켓몬을 기록에서 제거 • 𝑒1번 방에서 시작해 모든 종류를 잡을 때까지 전진 • 지나온 방의 개수 𝑥2 계산 • 최적해 = min (𝑥1, 𝑥2) 갱신 • 2번 방에서 잡은 포켓몬을 기록에서 제거 • 𝑒2번 방에서 시작해 모든 종류를 잡을 때까지 전진 • … • m번 방에서 잡은 포켓몬을 기록에서 제거 • 더 이상 모든 종류를 잡을 수 없으면 종료
  • 11. C. They Are Everywhere • Data.Set은 어떤 값이 집합에 소속되는지만 검사 가능 • Data.Map을 이용해서 키(포켓몬 종류)를 값(그 종류를 잡은 개수)으로 매핑
  • 12. import Data.List import Data.Array import Data.Maybe import qualified Data.Map.Strict as M main = do n <- read `fmap` getLine :: IO Int flats <- getLine let flatAry = listArray (1, length flats) flats let numPoke = (length . group . sort) flats print $ solve numPoke flatAry type Book = M.Map Char Int type Solution = (Book, Int, Int) -- (currSet, beginIx, endIx) solve numPoke flats = f (M.empty, 1, 1) 100000 where f :: Solution -> Int -> Int f curr best = case walk numPoke flats curr of Nothing -> best Just next -> f (removeHead next flats) (min best (cost next)) cost (_, b, e) = e - b removeHead (book, b, e) flats = (book', b+1, e) where x = flats ! b book' = if book M.! x == 1 then M.delete x book else M.adjust (y->y-1) x book walk :: Int -> Array Int Char -> Solution -> Maybe Solution walk num flats (book, b, e) = f book e where (ary1, aryN) = bounds flats len = aryN - ary1 + 1 f book e | e > len && M.size book >= num = Just (book, b, e) | e > len && M.size book < num = Nothing | M.size book == num = Just (book, b, e) | otherwise = f (M.insertWith (+) (flats ! e) 1 book) (e + 1) C. They Are Everywhere
  • 13. D. As Fast As Possible • http://codeforces.com/contest/701/problem/D • 키워드: ad hoc, 이진 탐색(binary search) • 문제: 일직선 도로의 한쪽 끝에 사람 n명, k명을 태울 수 있는 버스가 있다. 각자 버스를 최대 한 번만 탈 수 있다. 반대편 끝으로 도달하는 최소 시간을 구하여라. • 버스 탑승, 하차, 버스가 방향을 돌리는 것은 시간이 걸리지 않는다. • 사람 수 1 ≤ 𝑛 ≤ 100000 • 버스의 수용량 1 ≤ 𝑘 ≤ 𝑛 • 사람과 버스의 속도 1 ≤ 𝑣1 < 𝑣2 ≤ 109 • 도로 길이 1 ≤ 𝑙 ≤ 109
  • 14. D. As Fast As Possible • 이진 탐색을 이용한 해법: http://pakapa104.hatenablog.com/entry/2016/07/25/135255 • 하지만 식을 계산해서 O(1) 해답 도출 가능 • 사람들을 𝑚 = 𝑛 𝑘 개 그룹으로 분할한다. • 모든 그룹은 버스를 타는 시간이 x로 동일하다. • 걷는 것보다 버스를 타는 것이 무조건 빠르다 • 버스를 한 그룹이 x + e, 다른 그룹이 x – e 시간 탄다고 가정 (e > 0) • x - e 시간 타는 그룹은 e 시간만큼 더 걷게 된다 -> 더 오래 걸린다 • 따라서 모든 그룹은 버스를 타는 시간이 같아야 한다
  • 15. D. As Fast As Possible • 버스가 한 그룹을 태우고 갔다가 나머지 그룹에게 돌아오는 시간 y • 버스 전진: 𝑥𝑣2 • 보행자 전진: 𝑥𝑣1 • 거리 차이: 𝑥 𝑣2 − 𝑣1 • 버스 후진, 보행자 전진: y = 𝑥 𝑣2−𝑣1 (𝑣2+𝑣1) • 버스를 타는 시간 x의 최대값은? • 그룹 𝑔1, 𝑔2, … , 𝑔 𝑚 • 𝑔1은 버스를 x 시간 타고 남은 거리를 걸어간다 • 𝑔2은 x + y 시간을 걷고 버스를 x 시간 타고 남은 거리를 걸어간다 • 𝑔3은 2(x + y) 시간을 걷고 버스를 x 시간 타고 남은 거리를 걸어간다 • … • 𝑔 𝑚 이 x + y m − 1 시간을 걷고 남은 거리는 버스를 타기에 충분히 길어야 한다 ( ≥ 𝑥𝑣2)
  • 16. D. As Fast As Possible • 𝑔 𝑚은 x + y m − 1 시간을 걷고 남은 거리는 버스를 탄다 • 𝑔 𝑚이 걸은 거리: 𝑥 + 𝑦 𝑚 − 1 𝑣1 = x 1 + v2−𝑣1 𝑣2+𝑣1 𝑚 − 1 𝑣1 ≤ 𝐿 − 𝑥𝑣2 • 𝑥 ≤ 𝐿 𝑣2 +𝑣1 1+ v2−𝑣1 𝑣2+𝑣1 (𝑚−1) • x의 최대값은 𝐿 𝑣2 +𝑣1 1+ v2−𝑣1 𝑣2+𝑣1 (𝑚−1) • 𝑔1, 𝑔2, … , 𝑔 𝑚−1은 먼저 도착 • 𝑔 𝑚의 총 이동 시간 𝑥 + 𝐿 −𝑥𝑣2 𝑣1 이 답
  • 17. D. As Fast As Possible main = do ps@[n, l, v1, v2, k] <- (map read . words) `fmap` getLine :: IO [Int] let ps' = map fromIntegral ps :: [Double] print $ solve ps' solve :: [Double] -> Double solve [n, l, v1, v2, k] = t1 + x where m = fromIntegral (ceiling (n / k)) :: Double v3 = (v2 - v1) / (v2 + v1) x = l / (v2 + (m-1)*(v1 * (1+ v3))) t1 = (l - v2*x) / v1
  • 18. E. Connecting Universities • http://codeforces.com/contest/701/problem/E • 키워드: 그래프, 스패닝 트리, 깊이 우선 탐색(DFS), ByteString • 문제: 마을 n개가 간선 n-1개로 연결되어 스패닝 트리를 형성한다. 이 중 2k개 마을에 도시가 있고, 모든 간선의 가중치는 1이다. 도시들을 k개 짝으로 묶으려 하는데, 모든 짝의 거리의 합이 최대가 되도록 한다. 거리의 합의 최댓값을 구하여라. • 마을 수 2 ≤ 𝑛 ≤ 200000 • 도시 수 1 ≤ 2𝑘 ≤ 𝑛 • 참고 자료 • http://kenkoooo.hatenablog.com/entry/2016/07/23/043406 • http://blog.csdn.net/baidu_19306071/article/details/52005557
  • 19. E. Connecting Universities • 두 마을 u, v를 연결하는 임의의 간선에 대해 • u쪽에 있는 도시가 x개, v쪽에 있는 도시가 2k - x개라 하면 • u쪽의 도시와 v쪽의 도시를 짝지어야 한다 • 가능한 짝은 min(x, 2k - x) 개 • 한 쪽의 두 도시를 짝지으면 무조건 거리의 총합이 낮아진다 vu
  • 20. E. Connecting Universities • u쪽의 도시와 v쪽의 도시끼리 짝지을 경우 비용 P • 다음 두 비용의 합 • path(u1, u) + path(u, v) + path(v, v1) • path(u2, u) + path(u, v) + path(v, v2) • u쪽의 도시끼리, v쪽의 도시끼리 짝지을 경우 비용 Q • Q = path(u1, u2) + path(v1, v2) • Q <= path(u1,u) + path(u,u2) + path(v1, v) + path(v, v2) < P vu u1 u2 v1 v2
  • 21. E. Connecting Universities • 한 마을을 루트 삼아 순회 시작 • 마을마다 해당 마을을 포함해 DFS 트리상 그 아래에 있는 도시의 개수 x 계산 • 마을에 도시가 있으면 x = 1, 없으면 x = 0 으로 설정하고 자식 노드들의 x를 더한다 • 후위 순회 (postorder traversal) 필요 • 모든 마을의 min(x, 2k-x)를 더한 것이 답 • 한 간선에 저장된 값이 min(x, 2k-x) 인 것의 의미 • 이 간선을 지나는 경로가 min(x, 2k-x) 개 • 간선의 가중치(길이)는 1 • 모든 간선에 저장된 값을 더하면 모든 경로의 길이를 더한 것이 된다
  • 22. E. Connecting Universities • 입력이 너무 길어서 ByteString 사용 (String 쓰면 시간 초과) • -- getInts = (map read . words) `fmap` getLine :: IO [Int] • getInts = (map (fst . fromJust . BS8.readInt) . BS8.words) `fmap` BS.getLine :: IO [Int] • 그래프는 인접 리스트로 표현 • type Tree = Map.Map Int (Int, [Int]) -- (data, children) • 답이 Int의 범위를 넘어서 Integer 사용 • answer :: Tree -> Int -> Int -> Integer • Integer는 하스켈에 내장된 big integer 타입 • 답이 얼마나 큰지 따지기 귀찮을 때 사용. 계산 속도는 Int보다 느리다
  • 23. import Control.Monad import Data.Array import Data.Maybe import qualified Data.Map.Strict as Map import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 getInt = read `fmap` getLine :: IO Int --getInts = (map read . words) `fmap` getLine :: IO [Int] getInts = (map (fst . fromJust . BS8.readInt) . BS8.words) `fmap` BS.getLine :: IO [Int] type Tree = Map.Map Int (Int, [Int]) -- (data, children) main = do [n, k] <- getInts cities <- getInts edges <- replicateM (n-1) getInts let tree0 = Map.fromList [(key, (0,[])) | key <- [1..n]] :: Tree let tree = foldl addCity (foldl addEdge tree0 edges) cities let t = dfs tree 1 print $ answer t n (k*2) addEdge :: Tree -> [Int] -> Tree addEdge g [u,v] = g'' where g' = Map.update ((x,es) -> Just (x,u:es)) v g g'' = Map.update ((x,es) -> Just (x,v:es)) u g' addCity :: Tree -> Int -> Tree addCity g i = Map.update ((_,es) -> Just (1,es)) i g dfs :: Tree -> Int -> Tree dfs tree start = f 0 tree start where link curr prev = filter (/= prev) $ snd (tree Map.! curr) f prev tree curr | snd (tree Map.! curr) == [] = tree | otherwise = let x = foldl (f curr) tree (link curr prev) in seq x (calc x curr prev) calc tree curr prev = Map.update ((d,es) -> Just (d+cost,es)) curr tree where cost = sum $ map (j -> fst (tree Map.! j)) (link curr prev) :: Int answer :: Tree -> Int -> Int -> Integer answer tree n k2 = sum $ map (i -> min (cnt i) (k2' - (cnt i))) [1..n] where k2' = fromIntegral k2 cnt i = fromIntegral $ fst (tree Map.! i) E. Connecting Universities
  • 24. E. Connecting Universities • 시간 제한: 3초 • 메모리 제한: 256MB • 간신히 통과