けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ABC 050 C - Lining Up (3Q, 緑色, 300 点)

今の時代だとあまり見かけないタイプの数学ゲー問題。

問題概要

 1, 2, \dots, N を一列に並べる方法のうち、次の条件をすべて満たすものの個数を 1000000007 で割った余りを求めよ。

 i について、左側にいる人数と右側にいる人数の差が  A_{i} に等しい

制約

  •  1 \le N \le 10^{5}
  •  0 \le A_{i} \le N - 1

考えたこと

実は大半の  A について、答えは 0 通りなのではないかと思われた。というのも、各人の左側人数と右側人数との差分を小さい順にソートした値は、 N のみに依存するのだ。

  • 1 人のとき、 (0)
  • 2 人のとき、 (1, 1)
  • 3 人のとき、 (0, 2, 2)
  • 4 人のとき、 (1, 1, 3, 3)
  • 5 人のとき、 (0, 2, 2, 4, 4)
  • 6 人のとき、 (1, 1, 3, 3, 5, 5)
  • 7 人のとき、 (0, 2, 2, 4, 4, 6, 6,)
  • ...

という具合だ。 N が奇数のときは  (0, 2, 2, 4, 4, \dots) となり、 N が偶数のときは  (1, 1, 3, 3, 5, 5, \dots) となる。

よって、そもそも数列  A を小さい順にソートしたとき、上記の通りになっていなければ 0 通りなのだ。以降、 A をソートしたとき、 N に応じて上記の通りになっているものとする。また、適宜人の番号を入れ替えることにより、 A がソート済であるとしても一般性を失わない。

場合の数を求める

これ以降、 A を小さい順にソートして考えることにする。たとえば  N = 5 の場合を考えよう。このとき、 A = (0, 2, 2, 4, 4) であると考えてよい。このとき、場合の数は

  •  1, 5 がそれぞれ、左端にいくか、右端にいくか
  •  2, 4 がそれぞれ、左端から 2 番目にいくか、右端から 2 番目にいくか

によって、 2 \times 2 = 4 通りが考えられる。

同様に考えて、一般の  N についても、人  i, N+1-i がそれぞれ左端から i 番目にいくか、右端から i 番目にいくかをそれぞれ考慮することで、

 2 \times 2 \times \dots \times 2 = 2^{\frac{N}{2}} 通り

の場合が考えられる。

コード

#include <bits/stdc++.h>
using namespace std;
const int MOD = 1000000007;

int main() {
    int N;
    cin >> N;
    deque<int> A(N);
    for (int i = 0; i < N; i++) cin >> A[i];
    sort(A.begin(), A.end());

    if (N % 2 == 0) {
        for (int i = 0; i < N; i += 2) {
            if (A[i] != i+1 || A[i+1] != i+1) {
                cout << 0 << endl;
                return 0;
            }
        }
    } else {
        if (A[0] != 0) {
            cout << 0 << endl;
            return 0;
        }
        for (int i = 1; i < N; i += 2) {
            if (A[i] != i+1 || A[i+1] != i+1) {
                cout << 0 << endl;
                return 0;
            }
        }
    }

    long long res = 1;
    for (int i = 0; i < N / 2; i++) {
        res = (res * 2) % MOD;
    }
    cout << res << endl;
}