EDPC Y : Grid 2
問題文:https://atcoder.jp/contests/dp/tasks/dp_y
概要
日本語なので略。
解法
「1つも壁を通らない経路の個数」は、「全体の経路の個数」から「壁を1つ以上通る経路の個数」を引くことで求められる。
全体の経路の個数
全体の経路の個数は となる。
壁を1つ以上通る経路の個数
壁マス全体の集合をとする。また、任意のの部分集合について、
からに含まれる壁をすべて通ってにたどり着く経路の個数
とする。すると、包除原理より壁を1つ以上通る経路の個数はで求まる。
上記をまとめると、結局「1つも壁を通らない経路の個数」は と表される事がわかる。
よって、この包除原理の式の各項を計算していけば答えが求まる。しかし、項の個数は 個あるため、愚直に計算するのではなく効率的に和の計算を行う必要がある。
半順序関係の導入
ここで先程の を思い出してみよう。次の図のような関係にある壁がどちらもに含まれている場合、今回の問題設定では右と下方向にしか動けないためとなる。
となる は求めたい和を変えないため、そうでないについて考察してみよう。
となるためには、 個の壁マスが「左上から右下に並んでいる」必要がある。
もう少し厳密に書くと、 に半順序関係
かつ
を導入したときに、となるためにはの元だけで考えたときに が全順序になっている必要がある。この順序をもとに動的計画法の式を立式することを考える。
動的計画法の遷移式の立式
壁マスとからなる集合を考える。任意ののマスについて
( から まで偶数個の壁マスをたどって到達する方法の個数)
( から まで奇数個の壁マスをたどって到達する方法の個数)
とする*1。このとき、上記の定義より、漸化式
が成り立つ。
また、「1つも壁を通らない経路の個数」 は で求まる。
上記の漸化式はに属するマスを適切な順序で並び替えることにより計算することが可能である。
あるマスについて、順序関係がある別のマスの個数は個ある。
の元は個存在する。したがって 時間でこの問題を解くことができる。
コード
#include <cstdio> #include <vector> #include <algorithm> using namespace std; const int MAX_SIZE = 200000; const int MAX_N = 3000; const int MOD = 1000000007; typedef long long Int; Int fac[MAX_SIZE + 1]; Int inv[MAX_SIZE + 1], ifac[MAX_SIZE + 1]; Int comb(int n, int r) { Int ret = fac[n]; ret = (ret * ifac[r]) % MOD; ret = (ret * ifac[n - r]) % MOD; return ret; } Int dp[MAX_N + 2][2]; int main() { int h, w, n; scanf("%d %d %d", &h, &w, &n); inv[1] = 1; fac[0] = ifac[0] = 1; fac[1] = ifac[1] = 1; for (int i = 2; i <= h + w; i++) { fac[i] = (fac[i - 1] * i) % MOD; inv[i] = (inv[MOD % i] * (MOD - MOD / i)) % MOD; ifac[i] = ifac[i - 1] * inv[i] % MOD; } vector< pair<int, int> > pos; pos.push_back(make_pair(1, 1)); pos.push_back(make_pair(h, w)); for (int i = 0; i < n; i++) { int r, c; scanf("%d %d", &r, &c); pos.push_back(make_pair(r, c)); } sort(pos.begin(), pos.end()); dp[0][0] = 1; for (int i = 1; i < pos.size(); i++) { for (int j = 0; j < i; j++) { if (pos[j].first <= pos[i].first && pos[j].second <= pos[i].second) { int dr = pos[i].first - pos[j].first; int dc = pos[i].second - pos[j].second; Int coef = comb(dr + dc, dc); dp[i][0] = (dp[i][0] + dp[j][1] * coef) % MOD; dp[i][1] = (dp[i][1] + dp[j][0] * coef) % MOD; } } } printf("%lld\n", (dp[n + 1][1] - dp[n + 1][0] + MOD) % MOD); return 0; }
*1:厳密には:=( から まで、偶数個の壁マスにマークをつけて到達する方法の個数)としたほうが適切だが、表現がややこしいため上記では簡略化して記載した。