競プロ鉄則 Haskell AtCoder 振り返り7

に公開

A7

問題:
ある会社ではD日間にわたってイベントが開催され、N人が出席します
参加者iL_i日目からR_i日目まで出席する予定です
各日の出席者数を出力するプログラムを作成してください

制限時間 : 2000ms

制約:

  • 1≤D,N≤10^5
  • 1≤L_i≤R_i≤D
  • 入力はすべて整数

入力:
D
N
L_1 R_1
L_2 R_2
:
L_N R_N

出力:
D行にわたって出力してください。
d行目には d日目の出席者数を出力してください。

提出コード

import Control.Monad (replicateM, unless)
import Data.Array.MArray (newArray, readArray, writeArray, getElems)
import Data.Array.ST (STUArray)
import Control.Monad.ST (ST, runST)

inputIntList :: Int -> IO [[Int]]
inputIntList n = replicateM n getLine >>= return.(map ((map read).words))

main :: IO ()
main = do
  [ds,ns] <- inputIntList 2
  let d = head ds
  let n = head ns
  lrs <- inputIntList n
  let arr = aList d lrs
  mapM_ print (scanl1 (+) arr) 
  
aList :: Int -> [[Int]] -> [Int]       
aList d lrs = runST $ do
  arr <- newArray (1,d) 0 :: ST s (STUArray s Int Int)
  mapM_ (\[l,r]-> do 
             readArray arr l >>= \i -> writeArray arr l (i+1)
             unless (r==d) $ readArray arr (r+1) >>= \i -> writeArray arr (r+1) (i-1)) lrs
  getElems arr 

結果

AC 時間:293ms

徒然

最近AtCoderの abc(AtCoder Bigginer Contest) や adt(AtCoder Daily Training) をやるやうになつた
まだ10級だが 色々と勉強になってゐると思ふ

考察

なんと 去年の7月に書いたコードだった(今11月なので 一年半くらゐ前だ)
Mutable Array を使って 見やう見まねで 頑張って書いてゐるのが分かる
inputIntList のところからして なんか ゴチャゴチャしてゐる
それでも 何だか愛ほしい
とにかく 下手でも 不器用でも 自分で何とか考へて 進まうとしてゐるからだ
今は 若干 まう少しシンプルに書ける氣がするし それも結局 少しづつでも續けてきたからだらう

入力の部分は 今ならかう書く

import Control.Monad (replicateM)

ints :: IO [Int]
ints = map read . words <$> getLine

main :: IO ()
main = do
    d <- readLn :: IO Int
    n <- readLn :: IO Int
    lrs <- replicateM n ints

ints の部分は

import qualified Data.ByteString.Char8 as B
import Data.List (unfoldr)

ints :: IO [Int]
ints = unfoldr (B.readInt . B.dropSpace) <$> B.getLine

のやうに ByteString を使って書くことができ この方が速いとよく言はれるが
TLEになるのは Inputの遅さよりも もっと別の原因の方がずっと多いやうな氣がする
別の原因といふのは 主にアルゴリズムだ

この問題のやうに 日づけに對して 参加者を數へ上げていくといふとき
普通は 日程が1日目から10日目まであるとして
A君が 2日目から5日目まで参加する
B君が 3日目から8日目まで参加する
となった場合 日づけに對する参加者のデータを
0 0 0 0 0 0 0 0 0 0
とリセットしてから
A君のとき
0 1 1 1 1 0 0 0 0 0
と更新し
B君のとき
0 1 2 2 2 1 1 1 0 0
と更新する
といふやうに考へるだらう(自分はさうだった)

しかし これだと 日づけの量が 大量になったり 参加者が大量になったとき
参加者ごとに 結構な量のデータを更新しなければならず 時間がかかる

けれども これを
0 0 0 0 0 0 0 0 0 0
とリセットした状態から
A君が
0 1 0 0 0 -1 0 0 0 0
B君によって
0 1 1 0 0 -1 0 0 -1 0
と更新すれば 各々の参加者がデータを更新する回數は 2回だけで済む

そして 最後のデータの累積和をとれば
0 1 2 2 2 1 1 1 0 0
となり 最初の手法と結果が同じになる

かういふことを 自分であみだせ っていふのは 結構きつい
だから 「鉄則」とかで紹介されてゐるアルゴリズム手法といふものは まさに珠玉といふか
「人類の宝」みたいなものだらう

AtCoderに觝れれば觝れるほど コードの奥深さ 世界の廣さ 自分の未熟さを 思ひ知らされる

再提出

import Control.Monad (replicateM)
import qualified Data.IntMap as M
import Data.IntMap ((!))

ints :: IO [Int]
ints = map read . words <$> getLine

main :: IO ()
main = do
  d <- readLn :: IO Int
  n <- readLn :: IO Int
  lrs <- replicateM n ints
  let mp = M.fromList (map (,0) [1..(d+1)]) 
  let nmp = foldl updateMap mp lrs
  mapM_ print $ init $ scanl1 (+) (M.elems nmp)

updateMap :: M.IntMap Int -> [Int] -> M.IntMap Int
updateMap mp [l,r] = upd (+(-1)) (r+1) $ upd (+1) l mp
    where upd f = M.update (Just . f) 

https://atcoder.jp/contests/tessoku-book/submissions/71076584

結果

AC 時間:534ms

考察

immutable でもクリアできた
コードがよりシンプルでいいと思ふ

B7

問題:
あるコンビニは時刻0に開店し 時刻Tに閉店します
このコンビニにはN人の従業員が働いており i番目(1≤i≤N)の従業員は時刻L_iに出勤し 時刻R_iに退勤します
t=0,1,2,...,T-1それぞれについて 時刻t+0.5にコンビニにいる従業員の数を出力するプログラムを作成してください

制限時間 : 6000ms

制約:

  • 1≤T≤500000
  • 1≤N≤500000
  • 1≤L_i≤R_i≤T(1≤i≤N)
  • 入力はすべて整数

入力:
T
N
L_1 R_1
L_2 R_2
:
L_N R_N

出力:
全体でT行出力してください。
t+1行目(0≤t≤T-1)には 時刻t+0.5$にコンビニにいる従業員の数を出力してください

提出コード

import Control.Monad (replicateM)
import qualified Data.IntMap as M
import Data.IntMap ((!))

ints :: IO [Int]
ints = map read . words <$> getLine

main :: IO ()
main = do
  t <- readLn :: IO Int
  n <- readLn :: IO Int
  lrs <- replicateM n ints
  let mp = M.fromList (map (,0) [0..t]) 
  let nmp = foldl updateMap mp lrs
  mapM_ print $ init $ scanl1 (+) (M.elems nmp)

updateMap :: M.IntMap Int -> [Int] -> M.IntMap Int
updateMap mp [l,r] = upd (+(-1)) r $ upd (+1) l mp
    where upd f = M.update (Just . f) 

結果

AC 3971ms 568220KiB

考察

A7と實装はほとんど變はらない
IntMapの範囲が0からtになってゐるのと
更新する場所が l と r になった(A7では l と (r+1)) くらゐだ
これを MArrayで實装すると次のやうになる

再提出

import Control.Monad (replicateM)
import qualified Data.Array.MArray as A
import Data.Array.ST (STUArray)
import Control.Monad.ST (ST, runST)

ints :: IO [Int]
ints = map read . words <$> getLine

main :: IO ()
main = do
  t <- readLn :: IO Int
  n <- readLn :: IO Int
  lrs <- replicateM n ints
  let arr = aList t lrs
  mapM_ print $ init $ scanl1 (+) arr 

aList :: Int -> [[Int]] -> [Int]
aList t lrs = runST $ do   
  arr <- A.newArray (0,t) 0 :: ST s (STUArray s Int Int)
  mapM_ (\[l,r] -> do
            A.readArray arr l >>= A.writeArray arr l . (+1)
            A.readArray arr r >>= A.writeArray arr r . (+(-1))) lrs
  A.getElems arr

結果

AC 1922ms 409608KiB

考察

かかった時間が半分くらゐになった
さらに 入力の部分をBytestringでやったら 366ms になった
https://atcoder.jp/contests/tessoku-book/submissions/71086303

Discussion