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

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

ABC120 D Decayed BridgesをKotlinで

irisruneです。今回もKotlinで解いた過去問についてですが、少し重い問題なので1問だけです。

atcoder.jp

橋を減らしていくのではなく逆に増やしていくという発想ができれば愚直に解くことはできると思います。そして時間内に解くのはUnion-Findを知らなくてもできます(できました)、でもD問題にしては実装も重い気がします。

class Bridge(val a : Int, val b : Int)
class Island(var group : Int)

fun moveGroup(s : MutableList<Int>, t : MutableList<Int>, lIsland : MutableList<Island>, group : Int){
    for(i in 0..(s.size - 1)){
        t.add(s[0])
        lIsland[s[0]].group = group
        s.removeAt(0)
    }
}

fun main(args: Array<String>){
    val (n, m) = readLine()!!.split(" ").map(String::toInt)
    var lIsland = mutableListOf<Island>()
    var lGroup : MutableList<MutableList<Int>> = mutableListOf()
    for(i in 0..(n - 1)){
        lIsland.add(Island(i))
        val iLGroup = mutableListOf(i)
        lGroup.add(iLGroup)
    }
    var lBridge = mutableListOf<Bridge>()
    for(i in 0..(m - 1)){
        val(a, b) = readLine()!!.split(" ").map(String::toInt)
        lBridge.add(Bridge(a - 1, b - 1))
    }
    var lRet = mutableListOf<Long>()
    lRet.add(n.toLong() * (n - 1).toLong() / 2L)
    for(i in (m - 1) downTo 1){
        val groupA = lIsland[lBridge[i].a].group
        val groupB = lIsland[lBridge[i].b].group
        if(groupA == groupB){
            lRet.add(lRet[(m - 1) - i])
        }else{
            lRet.add(lRet[(m - 1) - i] - (lGroup[groupA].size * lGroup[groupB].size))
            if(lGroup[groupA].size >= lGroup[groupB].size) moveGroup(lGroup[groupB], lGroup[groupA], lIsland, groupA)
            else moveGroup(lGroup[groupA], lGroup[groupB], lIsland, groupB)
        }
    }
    for(i in (m - 1) downTo 0){
        println(lRet[i])
    }
}

この問題の実装面での課題は解説にもある通り、

  1. 頂点uが属するグループと頂点vが属するグループを併合し、同じグループにする操作
  2. 頂点vが属するグループ番号を得る操作
  3. 頂点vが属するグループと同じグループに属する頂点数を得る操作

以上の操作をどのように実装するかになります。愚直に実装するとO(N+M)時間かかる操作が出てしまうので(よくわかってない)、全体でO(M(N+M))時間かかってしまい到底時間内には解けません。

今回自分はどのようにこれらの操作について実装したかというと、

  1. 頂点数の少ないグループの頂点を頂点数の多いグループに移動させる
    • 全体でO(N)時間に。
  2. 頂点にグループ番号を持たせる
    • 1操作あたりO(1)時間に。
  3. グループごとにリストを作ってそこに頂点を入れてsizeを用いる
    • 1操作あたりO(1)時間に。

以上のように実装することで全体でO(N+M)時間に抑えたので、無事ACできました。でもこの実装がUnion-Findかというと多分違うと思います。

今回のコードレビューは、

  • varを多用しすぎ。
  • 一つの変数にIntを使うべき場所とLongを使うべき場所の両方が存在してしまい。toLong()を使う必要が出ている。
  • クラスの作りが非常に雑、こんなクラスならなくていい。

ざっと見た感じだとこのくらいですかね。多分もっと突っ込みどころありそうですね。

雑記

  • ACできたとは書きましたが、1800msくらいかかっていたので多分入力がボトルネックになっています。でも入力を実装するとブログ上にコード貼り付けるときに困るんですよね…

  • ABC120 C Unificationは愚直に解いてTLE出し続けました。他人の実行時間を見てから解けたので事実上自力ではないです。