あるエンジニアのAtCoder奮闘記

東京都港区にあるアミフィアブル株式会社のエンジニアが、AtCoderで解いた問題について振り返ったりしていく会社公認のブログです。

AtCoder ABC 127 D - Integer Cards

irisruneです。今日は試しに2記事公開してみました。

atcoder.jp

アルゴリズムの発想と計算量の抑え方の両面が重要となる問題だと思いました。半分嘘解法です。

package main

import (
    "bufio"
    "fmt"
    "os"
    "sort"
    "strconv"
)

var sc *bufio.Scanner

func nextInt() int {
    sc.Scan()
    i, e := strconv.Atoi(sc.Text())
    if e != nil {
        panic(e)
    }
    return i
}

type operation struct {
    b int
    c int
}

type ope []operation

func (o ope) Len() int {
    return len(o)
}

func (o ope) Swap(i, j int) {
    o[i], o[j] = o[j], o[i]
}

func (o ope) Less(i, j int) bool {
    return o[i].c < o[j].c
}

func main() {
    sc = bufio.NewScanner(os.Stdin)
    sc.Split(bufio.ScanWords)
    n, m := nextInt(), nextInt()

    aArray := make([]int, n)
    for i := 0; i < n; i++ {
        aArray[i] = nextInt()
    }

    var o ope = make([]operation, m)
    for j := 0; j < m; j++ {
        o[j].b, o[j].c = nextInt(), nextInt()
    }
    sort.Sort(sort.Reverse(o))

    cnt := 0
    for j := 0; j < m && cnt < n; j++ {
        for k := 0; k < o[j].b && cnt < n; k++ {
            aArray = append(aArray, o[j].c)
            cnt++
        }
    }
    sort.Sort(sort.Reverse(sort.IntSlice(aArray)))

    ans := 0
    for i := 0; i < n; i++ {
        ans += aArray[i]
    }

    fmt.Println(ans)
}

ベースとなる考え方は、元々あるN枚のカードとj=1,2,\dots,Mに対応する整数C_jの書かれたB_j枚のカードを合わせた中から、大きい順にN枚取り出した整数の合計を求めるというものです。 ただし制約条件が1\leq M\leq 10^5,1\leq B_j\leq N\leq 10^5であるため、最終的なカードの枚数は最大で10^{10}枚を上回ります。つまり安直にカードの集合を足し合わせてソートするとTLEしてしまいます。

それを解消するために取った方法が、集合に足し合わせるカードの枚数にN枚という上限を設けるものでした。これによりソート処理でTLEすることはなくなるため問題を解くことができます。

公式解説にあるように、足し合わせるカードを1枚ずつバラさずに集合のまま扱うとすべての集合を足し合わせた上でソートできるため記述はかなり易しくなると思います。

ちなみに、本コードの計算量はO(M\log M + N\log N)となります。実際は後半が2Nであり、またカード追加の際に枚数で逐一判定しているため公式解説と比べると定数倍遅いと思います。

雑記

  • 対談会の記事は先週公開と書いていましたが、今週予定に変更します。