🐷

AtCoder Beginner Contest 330参加記(A~E)

2023/11/26に公開

トヨタシステムズプログラミングコンテスト2023(AtCoder Beginner Contest 330)に参加したので記録を残します。
https://atcoder.jp/contests/abc330

今回は4完です。Eはコンテスト後に解いたものです。

A - Counting Passes

オーソドックスなA問題ですね。filterして残った件数が答えです。

fun main() {
    val (n, l) = readln().split(" ").map { it.toInt() }
    val a = readln().split(" ").map { it.toInt() }

    println(a.filter { it >= l }.size)
}

https://atcoder.jp/contests/abc330/submissions/47890196

B - Minimize Abs 1

オーソドックスなB問題… じゃなかったですね。まず問題文がけっこう難しくて読み取るのに時間がかかりました。さらに、問題文の意味がようやくわかっても、今度は明らかに全探索だと無理な制約であることに気づきます。LからRを一通り見るだけでも間に合わない…

あれこれ考えて、要するに|Xi - Ai|が最小になるものを求めればいいかなと思い至りました。そう考えると、AiLからRの中に含まれれば|Xi - Ai|を0にできるので、その場合はAi自体が答えになるとわかりました。
それなら場合分けすればよさそう。あとはAiLより小さい場合と、AiRより大きい場合がわかればよいです。AiLより小さい場合は、XiRに近づけると最小値から遠ざかるのでLが答えです。AiRより大きい場合は、逆にXiLに近づけると最小値から遠ざかるのでRが答えです。(絶対値なので)

import kotlin.math.abs
import kotlin.math.absoluteValue

fun main() {
    val (n, l, r) = readln().split(" ").map { it.toInt() }
    val a = readln().split(" ").map { it.toInt() }

    val ans = mutableListOf<Int>()
    for(i in 0 until n) {
        if(a[i] in l..r) {
            ans.add(a[i])
        } else if(a[i] < l) {
            ans.add(l)
        } else {
            ans.add(r)
        }
    }

    println(ans.joinToString(" "))
}

fun binarySearch(minNg: Long, maxOk: Long, isOk: (Long) -> Boolean): Long {
    var ng = minNg
    var ok = maxOk

    while(abs(ok - ng) > 1) {
        val mid = (ok + ng) / 2
        if(isOk(mid)) {
            ok = mid
        } else {
            ng = mid
        }
    }
    return ok
}

https://atcoder.jp/contests/abc330/submissions/47908476

試行錯誤の途中で混入したいらないコードが入っていますが…
ここまでで19分くらい費やしてしまいました。

C - Minimize Abs 2

Bと違ってすごくシンプルな問題文です。しかしこういう問題のほうがけっこう難しかったりしがちなイメージ…
まずは全探索を考えましたが、xyの組み合わせの全探索はダメそうだけど、片方だけ全探索ならできそうだと思いました。0から1000000まで探索すればよさそうと思いました。(と思ってペナりました… 実際には2000000まで探索が必要)

xが決まったらx^2からDを引いて、その値に最も近いy^2を見つければいいです。それを探すために、y^2としてあり得る値のリストを事前に作っておいて、それの中から二分探索で探すということをしています。まあ、本当はそんなことしなくても計算で求めればよかったのですが…

import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.min

fun main() {
    val d = readln().toLong()

    val ans = solve(d)
    println(ans)
}

fun solve(d: Long): Long {
    val limit = 5000000L
    val list = (1L..limit).map { it * it }

    var ans = Long.MAX_VALUE
    for(x in 0L..limit) {
        val x2 = x * x
        val diff = x2 - d

        if(diff >= 0) {
            ans = min(ans, diff)
        } else {
            val absDiff = abs(diff)
            val tmpIndex = list.binarySearch(absDiff)
            if(tmpIndex >= 0) {
                return 0L
            } else {
                val index = -(tmpIndex + 1)
                val beforeDiff = if(index - 1 >= 0) {
                    (list[index-1] - absDiff).absoluteValue
                } else {
                    Long.MAX_VALUE
                }
                val afterDiff = if(index < list.size) {
                    (list[index] - absDiff).absoluteValue
                } else {
                    Long.MAX_VALUE
                }

                val minDiff = min(beforeDiff, afterDiff)
                ans = min(ans, minDiff)
            }
        }
    }
    return ans
}

https://atcoder.jp/contests/abc330/submissions/47933911

最初は1000000Lまで探索するコードを投げてWAを出しました。愚直回との照合とかしてもなかなかダメなケースを見つけられず、もしかしたらと思って雑に5000000Lに増やして投げたら通ったという…
まだまだです。

D - Counting Ls

Cを解いた時点でもう時間が残り少なくて、Dは無理かと思っていましたが一応考えてみました。
まず、全マスを全探索するのは問題ないことを確認。それで、求めるとしたら各マスごとの値を求めて合計値を出せばよさそうかなと。各マス(oであるマス)一つ一つに注目すると、それと同じ行に(そのマスも含めて)2以上oがあり、かつ同じ列にも(そのマスも含めて)2以上oがあれば条件を満たします。それぞれちょうど2つの場合が最低条件で、その場合のそのマスに対する答えは1ですね。じゃあoが2より大きい場合はどうなるのか?と考えると、1増えるたびに掛け合わせで増えていくことが想像できました。3マスのうち1マスでも異なれば区別するわけなので。

じゃあ掛け算してそれの合計値を求めたらそれっぽいよねーと思ってその通りコードを書いてみたらなんとサンプルが合う。も、もしかしてと提出してみたら通りました。やったぜ。

fun main() {
    val n = readln().toInt()
    val s = List(n) {
        readln()
    }

    val hCount = IntArray(n)
    val vCount = IntArray(n)

    for(i in 0 until n) {
        for(j in 0 until n) {
            if(s[i][j] == 'o') {
                hCount[i]++
                vCount[j]++
            }
        }
    }

    var ans = 0L
    for(i in 0 until n) {
        for(j in 0 until n) {
            if(s[i][j] == 'o' && hCount[i] >= 2 && vCount[j] >= 2) {
                ans += hCount[i].minus(1) * vCount[j].minus(1)
            }
        }
    }

    println(ans)
}

https://atcoder.jp/contests/abc330/submissions/47938836

E - Mex and Update

Dを解いた時点でもう10分も残ってなかったのでさすがにEは無理でしたが、しかし見た感じ頑張ればいけそうな感触を得ました。なのでコンテスト後に解説は見ずにやってみました。1ペナ出しましたが解けたので、Cで時間を溶かさなければいけた可能性が…

基本的に、クエリで指定された通りの操作を実際にやればいいように思いました。最初の時点の答えは事前に調べておけばわかるので、あとは入力に合わせて答えを更新するかどうかを考えていけばいいのかなと。具体的には、クエリごとにi_kにもともとあった数値が1つ減り、x_kの値が1つ増える。
いちいち答えを探したらもちろん間に合わないので、答えとしてあり得る値を全部事前に調べておけばいいと思いました。TreeSetで管理して、都度最小値を出せばよさそうと。ただ、TreeSetだと重複を管理できないので、個数を管理するMapを別途作りました。
i_kにあった値は上書きされてなくなりますので、それが最後の1個だった場合は新たなmexとなる可能性があります。なのでその場合はTreeSetに追加します。
x_kとして渡された値はその時点ではmexではあり得ないのでTreeSetから削除します。(下記コードでは、性能を考えて必要な場合だけ削除するようにしています)

import java.io.PrintWriter

fun main() {
    val (n, q) = readln().split(" ").map { it.toInt() }
    val a = readln().split(" ").map { it.toInt() }.toMutableList()

    val aSet = a.toSet()

    val mexCandidates = sortedSetOf<Int>()

    val counts = mutableMapOf<Int, Int>()

    for(i in 0..2 * 100000 + 1) {
        if(i !in aSet) {
            mexCandidates.add(i)
        }
    }

    a.forEach {
        counts[it] = counts.getOrDefault(it, 0) + 1
    }

    val br = System.`in`.bufferedReader()
    val out = PrintWriter(System.out)
    repeat(q) {
        val (i, k) = br.readLine().split(" ").map { it.toInt() }

        val current = a[i-1]
        counts[current] = counts[current]!! - 1
        counts[k] = counts.getOrDefault(k, 0) + 1

        if(counts[current] == 0) {
            mexCandidates.add(current)
        }
        if(counts[k] == 1) {
            mexCandidates.remove(k)
        }

        a[i-1] = k

        out.println(mexCandidates.first())
    }

    out.close()
}

https://atcoder.jp/contests/abc330/submissions/47956817

よく見たら、kじゃなくてxが正しいですね… まあいいか。

TreeSetは各操作をO(log N)で実行してくれるので高速!logは定数!

あ…… うん

感想

BCで詰まったけどやっぱり解けると楽しい!レート的にも、半年ぶりにHighestを更新できてよかったです。ただ、現状は知識がおそらく偏っているので安定してこのような結果はなかなか出せないんですよね。ムラがある。
レート的にもそうですが、そもそも解けないと楽しくないというのもあり…
と考えると、知識が偏っていると解けるかどうか運に大きく左右されるのでやはりよくないなと思いました。だいたいどんな分野の問題でも、少なくともCまでなら確実に解けるようにしたいですね。それも高速に解きたい。時間が多く残れば上位の問題も解ける可能性が出てきますし、解けなくても解けないなりに考察しておけばupsolveもしやすくなりますし。

ということで、満遍なく知識を得られるように鉄則本を読んでいます。まだ最初のほうなので前回、今回が好調だったのは偶然です。効果が出るとしたらもっと先ですね。効果が出るといいなあ…

(執筆時間: 1時間6分37秒)

Discussion