CODE FESTIVAL 2016 Grand Final: J - 123 Pairs

問題概要

 1 から  2N までの  2N 個の数を  2 つずつ N 個のペアにするとき,差が  1 のペアが  A 個,差が  2 のペアが  B 個,差が  3 のペアが  C 個あるようなペアの作り方は何通りあるか。

(問題文は https://atcoder.jp/contests/cf16-exhibition-final/tasks/cf16_exhibition_final_j に記載されている。)

制約

  •  1 \leq N \leq 5000
  •  0 \leq A, B, C
  •  A + B + C = N

アイディア

まず,差が  1 のペアと差が  3 のペアは共に偶奇の異なる数同士がペアになるため,偶奇の一致する数同士がペアになることになる差が  2 のペアは偶数個である必要がある。つまり  B \bmod 2 = 1 の場合答えは  0 となる。

以下  B \bmod 2 = 0 の場合のみを考え,  b = \dfrac{B}{2} とする。

実は,このようなペアの作り方は以下の  4 種類のペアの組み合わせの並び替えで構成される。

  1. 差が  3 のペア  3 つが組み合わさっている。 (例: 1 - 4, 2 - 5, 3 - 6 )
  2. 差が  3 のペア  1 つの間に差が  1 のペアが  1 つ挟まっている。(例: 1 - 4, 2 - 3 )
  3. 差が  2 のペア  2 つの間に差が  3 のペアが  0 個以上挟まっている。(例: 1 - 3, 2 - 5, 4 - 7, 6 - 8 )
  4. 差が  1 のペア単独 。(例: 1 - 2 )

差が  3 であるような  C 個のペアのうち,タイプ  1 とタイプ  2 の構成に関わるペアの個数をそれぞれ  3x, y とする。 このとき,タイプ  1,タイプ 2,タイプ  3,タイプ 4のペアの組み合わせはそれぞれ x, y, b, A-yである。よってペアの組み合わせの並び替え方は  \dbinom{A+b+x}{x} \times \dbinom{A+b}{b} \times \dbinom{A}{y} であり, C-3x-y 個のペアのタイプ  3 への割り振り方は, C-3x-y 個の石と b-1 個の仕切りを並べることを考えて  \dbinom{C-3x-y+b-1}{C-3x-y} であるので  O(1) で求められる。 あとは考えられる  x, y をすべて試せばよい。

実装

二項係数をメモ化再帰で実装すると楽かもしれない。

 C-3x-y 個の石と b-1 個の仕切りを並べることを考えて」の部分で地味にバグらせやすいので注意。

コード

#include "bits/stdc++.h"
using namespace std;

typedef long long ll;
const ll MOD = (ll)(1e9+7);

ll memo[5050][5050];
bool done[5050][5050];

ll comb(int n, int k){
    if(n < k){
        return 0ll;
    }
    if(done[n][k]){
        return memo[n][k];
    }
    done[n][k] = true;
    if(k==0 || k==n){
        return memo[n][k] = 1ll;
    }
    memo[n][k] = (comb(n-1,k-1)+comb(n-1,k))%MOD;
    return memo[n][k];
}

int main(void){
    int N,A,B,C,b,x,y,z,m;
    ll ans=0ll,tmp;
    cin >> N >> A >> B >> C;
    if(B&1){
        cout << 0 << endl;
        return 0;
    }
    b = B>>1;
    for(x=0; x*3<=C; ++x){
        m = min(A,C-x*3);
        for(y=0; y<=m; ++y){
            z = C - x*3 - y;
            tmp = comb(A+b+x,x);
            tmp *= comb(A+b,b); tmp %= MOD;
            tmp *= comb(A,y); tmp %= MOD;
            if(b){
                tmp *= comb(z+b-1,z); tmp %= MOD;
            }else if(z){
                tmp = 0ll;
            }
            ans += tmp; ans %= MOD;
        }
    }
    cout << ans << endl;
    return 0;
}

(提出したものはこちら)

感想

急に解法が降ってきて嬉しかったので共有しておきます。