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

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

フォルシアゆるふわ競プロオンサイト #3 F - Yet Another Cake Division locked

面白かった

問題へのリンク

問題概要

 H \times W の盤面の各マスを T, M, N, P の四色に塗る方法のうち、以下の条件を満たすものが何通りあるか、1000000007 で割ったあまりを求めよ。

  • どの T マスと M マスについても、M マスは T マスから見て strict に右にあるか、strict に上にあるかのどちらかである
  • どの M マスと N マスについても、N マスは M マスから見て strict に右にあるか、strict に下にあるかのどちらかである
  • どの N マスと P マスについても、P マスは N マスから見て strict に左にあるか、strict に下にあるかのどちらかである
  • どの P マスと T マスについても、T マスは P マスから見て strict に左にあるか、strict に上にあるかのどちらかである
  • どの T マスとどの N マスについても、N マスは T マスから見て strict に右にある
  • どの M マスとどの P マスについても、P マスは M マスから見て strict に下にある

f:id:drken1215:20200301002902p:plain

制約

  •  2 \le H, W \le 10^{6}

考えたこと

まともにこの問題の考察をする前に解説を聞いてしまったので...とりあえず、ほとんどの場合

  • 左上から右下への最短路
  • 左下から右上への最短路

によって形成される 4 つの領域をそれぞれ T, M, N, P に塗り分ける方法と対応する模様。ただし、最短路の様相によってはいずれかの領域が潰れてしまうことがある。そういったものを引くことにする。

2 つの最短路の重なり

一般に 2 つの最短路は、

  • 1 点
  • 横方向の線分
  • 縦方向の線分

のいずれかになる。このうち 4 領域のいずれかが潰れるのは、以下のような場合になる。

  1. 上辺、右辺、下辺、左辺のいずれかに含まれる形の線分
  2. それ以外で、上辺、右辺、下辺、左辺のいずれか (ただし四隅を含まない) に端点をもつ線分

2 については上辺の内点と下辺の内点を結ぶような線分も含まれることに注意。そういうのをダブルカウントしないように気をつける。また、上辺、右辺、下辺、左辺上の 1 点のみが共有点となるようなケースは存在しないことに注意する。

1 について

上辺についてのみ考える。上辺から共有線分を除いた分の長さを  a とすると、 a = 0, 1, \dots, W-1 のいずれかの値をとりうる。そして  a を固定したときの場合の数は、実は

  •  C(2H-1 + a, 2H-1)

に等しい。なぜならその場合の数は、下図の点線より下の部分の斜め矢印の移動経路の場合の数に等しく、それは幅 1 の長方形を間に挟んで折り返して考えると  2(H-1) \times a の長方形の最短経路の本数に等しいからだ。

f:id:drken1215:20200301130605p:plain

というわけでこの値を  a = 0, 1, \dots, W-1 について合算すればよく、その値は

  •  C(2H-1 + W, W-1)

となる。よって 1 の場合の総和は、四辺について合算して

  •  2(C(2H-1+W, W-1) + C(2W-1+H, H-1))

となる。

2 について

上辺の内点を端点にもつ場合を考える。先ほどと同様にして、

  •  C(2H-1+W, W) - 2C(H-1+W, W)

通りとなる。「-  2C(H-1+W, W)」は左端点と右端点を共有点にもつ場合を除いた。この場合は 1 に含まれるので除いておく。これらを四辺すべてに適用して

  •  2(C(2H-1+W, W) + C(2W-1+H, H)) - 4(C(H-1+W, W) + C(W-1+H, H))

通りとなる。なお、

  •  C(H-1+W, W) + C(W-1+H, H) = C(H+W-1, H-1) + C(H+W-1, H) = C(H+W, H)

とまとめることもできる。ここからさらにダブルカウントを除く。

  • 上辺と下辺をともに内点にもつものは  W-1 通り
  • 左辺と右辺をともに内点をもつものは  H-1 通り

これらを除いて、すべて合わせると

  •  2(C(2H-1+W, W) + C(2W-1+H, H)) -  4C(H+W, H) - W - H + 2

通りということになる。

まとめ

まとめられるところはまとめてしまおう。1 と 2 とで

  •  C(2H-1+W, W-1) + C(2H-1+W, W) = C(2H+W, W)
  •  C(2W-1+H, H-1) + C(2W-1+H, H) = C(2W+H, H)

となる。これを踏まえてまとめると、答えは次のようになる。


 C(H+W, H)^{2} - 2(C(2H+W, W) + C(2W+H, H)) + 4C(H+W, H) + H + W - 2 通り


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

template<int MOD> struct Fp {
    long long val;
    constexpr Fp(long long v = 0) noexcept : val(v % MOD) {
        if (val < 0) val += MOD;
    }
    constexpr int getmod() { return MOD; }
    constexpr Fp operator - () const noexcept {
        return val ? MOD - val : 0;
    }
    constexpr Fp operator + (const Fp& r) const noexcept { return Fp(*this) += r; }
    constexpr Fp operator - (const Fp& r) const noexcept { return Fp(*this) -= r; }
    constexpr Fp operator * (const Fp& r) const noexcept { return Fp(*this) *= r; }
    constexpr Fp operator / (const Fp& r) const noexcept { return Fp(*this) /= r; }
    constexpr Fp& operator += (const Fp& r) noexcept {
        val += r.val;
        if (val >= MOD) val -= MOD;
        return *this;
    }
    constexpr Fp& operator -= (const Fp& r) noexcept {
        val -= r.val;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr Fp& operator *= (const Fp& r) noexcept {
        val = val * r.val % MOD;
        return *this;
    }
    constexpr Fp& operator /= (const Fp& r) noexcept {
        long long a = r.val, b = MOD, u = 1, v = 0;
        while (b) {
            long long t = a / b;
            a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        val = val * u % MOD;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr bool operator == (const Fp& r) const noexcept {
        return this->val == r.val;
    }
    constexpr bool operator != (const Fp& r) const noexcept {
        return this->val != r.val;
    }
    friend constexpr ostream& operator << (ostream &os, const Fp<MOD>& x) noexcept {
        return os << x.val;
    }
    friend constexpr Fp<MOD> modpow(const Fp<MOD> &a, long long n) noexcept {
        if (n == 0) return 1;
        auto t = modpow(a, n / 2);
        t = t * t;
        if (n & 1) t = t * a;
        return t;
    }
};

template<class T> struct BiCoef {
    vector<T> fact_, inv_, finv_;
    constexpr BiCoef() {}
    constexpr BiCoef(int n) noexcept : fact_(n, 1), inv_(n, 1), finv_(n, 1) {
        init(n);
    }
    constexpr void init(int n) noexcept {
        fact_.assign(n, 1), inv_.assign(n, 1), finv_.assign(n, 1);
        int MOD = fact_[0].getmod();
        for(int i = 2; i < n; i++){
            fact_[i] = fact_[i-1] * i;
            inv_[i] = -inv_[MOD%i] * (MOD/i);
            finv_[i] = finv_[i-1] * inv_[i];
        }
    }
    constexpr T com(int n, int k) const noexcept {
        if (n < k || n < 0 || k < 0) return 0;
        return fact_[n] * finv_[k] * finv_[n-k];
    }
    constexpr T fact(int n) const noexcept {
        if (n < 0) return 0;
        return fact_[n];
    }
    constexpr T inv(int n) const noexcept {
        if (n < 0) return 0;
        return inv_[n];
    }
    constexpr T finv(int n) const noexcept {
        if (n < 0) return 0;
        return finv_[n];
    }
};

const int MOD = 1000000007;
using mint = Fp<MOD>;
BiCoef<mint> bc;

int main() {
    bc.init(3100000);
    int H, W;
    cin >> H >> W;
    mint res = bc.com(H+W, H) * bc.com(H+W, H)
        - (bc.com(H*2+W, W) + bc.com(W*2+H, H)) * 2
        + bc.com(H+W, H) * 4
        + H + W - 2;
    cout << res << endl;
}