Leverage Copy

メモの公開場所

Codeforces Round No.593 (Div.2) 参加記録(A〜C解答)

Cの構築難しい。。と思ってたらみんなはやすやすと通していて驚きました。

構築は簡単なものでも刺さらないとずっと解けないので、筋の良い考え方のパターンをためていきたいところ。

※Dは実装方法が参考になりそうなので、取り組み次第追記します。

A. Stones

問題URL

問題の要約

3つの石が積まれた山から、2通りの決まった石の取り除き方を実行したとき、最大で何個取れるか?という問題。

解答

2番目の方法でできる限りたくさん取って、取れなくなったら今度は1番目の方法でできる限りたくさん取る、でOK。

以下は本番中に考えた直感的な証明。

それぞれ山をA, B, Cとすると、Cから石を取得する方法は2番目の「Bから1つCから2つ」という方法しかない。 もう片方の石のとり方は「Aから1つBから2つ」というものなので、Bの山の減り方に着目すると、先に2番目の方法で取り尽くしたほうが良い。

制約が小さいので、シミュレーションで書きました。

var t int

func main() {
    t = ReadInt()

    for i := 0; i < t; i++ {
        a, b, c := ReadInt3()

        ans := 0
        for b >= 1 && c >= 2 {
            b--
            c -= 2
            ans += 3
        }
        for a >= 1 && b >= 2 {
            a--
            b -= 2
            ans += 3
        }
        fmt.Println(ans)
    }
}

B. Alice and the List of Presents

問題URL

問題の要約

n 種類のプレゼントを m 人の子どもたちに振り分ける方法は何通りか?という問題。

ただし、ルールが2つあって、

  1. ある種類のプレゼントについて、ある1人に割り振る数は0個か1個のいずれか。
  2. ある種類のプレゼントについて、必ず最低1人には1個割り振る必要がある。

解答

ある1種類のプレゼントについて考えたとき、の m 人の子どもたちへの割り当て方を、 m 人の子供をビット列として見立てるとスッキリする。

ビットが立っているときにプレゼントを1個割り振ると考えると、 1 ~ (2^m-1) の割り振り方がある(2の制約から、すべてのビットが0の場合はNG)。

よって、あるプレゼントについては割り振り方が (2^m - 1) 通りあり、 どのプレゼントについてもこれは同じなので、 積事象を考えて (2^m - 1) ^ n が答え。

modpowスニペットにしているので、それを呼び出して終わり。。

※64bit整数で計算しないとオーバーフローでWAするので注意しましょう(1敗)。

const MOD = 1000000000 + 7

var n, m int64

func main() {
    n, m = ReadInt64_2()

    tmp := modpow(2, m, MOD)
    tmp = NegativeMod(tmp-1, MOD)
    ans := modpow(tmp, n, MOD)
    ans %= MOD
    fmt.Println(ans)
}

// ModInv returns $a^{-1} mod m$ by Fermat's little theorem.
// O(1), but C is nearly equal to 30 (when m is 1000000000+7).
func ModInv(a, m int64) int64 {
    return modpow(a, m-2, m)
}

func modpow(a, e, m int64) int64 {
    if e == 0 {
        return 1
    }

    if e%2 == 0 {
        halfE := e / 2
        half := modpow(a, halfE, m)
        return half * half % m
    }

    return a * modpow(a, e-1, m) % m
}

// NegativeMod can calculate a right residual whether value is positive or negative.
func NegativeMod(val, m int64) int64 {
    res := val % m
    if res < 0 {
        res += m
    }
    return res
}

C. Labs

問題URL

問題の要約

n^2 個のラボを n 個のラボからなるグループ n 個に分割したとき、 問題で定義された6パターンの関数値の最小値が最大となるような分割方法を答える問題。

解答

※後半かなり雑です。

あるグループA, B間の関数値を考えたとき、 n * n 通りの大小関係の比較が起こることから、 水の流れる総量は n^2 となる。

なので f(A, B) = x のとき f(B, A) = n^2 - x となる。

よって、最大化した最小値は floor(n^2 / 2) としかならないはず。 このような構築方法は無いかと考える。

。。ここまではコンテスト中かなり時間がかかったものの、気づけて、 そこからは無証明の推測で解きました。

n = 4 の場合を考えると、もとの行列で縦のグループを観たとき、 左から1番目のグループと2番目のグループを比較すると、目的の値に対して不足分があります。

足りないのは floor(n / 2) で、上 floor(n / 2) 行を反転させれば辻褄があう、と考えました。

f:id:maguroguma:20191027013314j:plain
元行列からの構築方法

一応、コンテス中は n = 5 のケースも考えてあっていることを確認し提出しました。

実際には、行ごとに考えたときに矢印の流れが均等になっている(半分の行では矢印が右向きに流れて、もう半分は左向きに流れる)ことに気づくことができれば確信を持てそうです。 下の行から上の行へは矢印は伸びず、逆を考えた場合は必ず矢印が伸びるので、行間では特にグループ間の差を考える必要はありません。

var n int
var answers [][]int

func main() {
    n = ReadInt()

    r := n / 2
    answers = make([][]int, n)
    for i := 0; i < n; i++ {
        answers[i] = make([]int, n)
    }
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            answers[i][j] = i*n + (j + 1)
        }
    }
    for i := 0; i < r; i++ {
        answers[i] = Reverse(answers[i])
    }

    for j := 0; j < n; j++ {
        for i := 0; i < n; i++ {
            if i == n-1 {
                fmt.Printf("%d\n", answers[i][j])
            } else {
                fmt.Printf("%d ", answers[i][j])
            }
        }
    }
}

func Reverse(A []int) []int {
    res := []int{}

    n := len(A)
    for i := n - 1; i >= 0; i-- {
        res = append(res, A[i])
    }

    return res
}

あくまでもコンテスト中の思考整理のために書いた方法なので、 模範解答はEditorialのもとの行列をジグザグにスキャンして各グループに配置する方法がベストかと思います。


「ジグザグに見る」っていうのをパターン化するのは賢くはないが、対症療法にはなりえるかも?