座圧に見えてしまった
問題概要
のグリッドがあります。グリッドのうち マスに壁があって壁の位置は で与えられます。壁のあるマスには行けません。
マス からマス へと至る最短経路が何通りあるか、 で割ったあまりを求めよ。
制約
考えたこと
包除原理で解く。
- すべての場合の数
- 「1 個の壁を選び、それらの壁を通る経路の数」を引く
- 「2 個の壁を選び、それらの壁を通る経路の数」を足す
- 「3 個の壁を選び、それらの壁を通る経路の数」を引く
- ...
とやっていく。次のような DP する
- dp[ i + 1 ][ j ] := i 個目の壁までを見て、i 番目の壁を最後に使って、j 個使う場合の場合の数
とすればよい。ただこのままだと になってしまうので、代わりに
- dp[ i + 1 ][ 偶奇 ] := i 個目の壁までを見て、i 番目の壁を最後に使って、(偶奇) 個使う場合の場合の数
とすればいい。これで になる。注意点として、 は昇順に並べておく (キーは でも でもいい)
#include <iostream> #include <vector> #include <algorithm> using namespace std; using pint = pair<int,int>; const int MAX = 210000; const int MOD = 1000000007; long long fac[MAX], finv[MAX], inv[MAX]; void COMinit(){ fac[0] = fac[1] = 1; finv[0] = finv[1] = 1; inv[1] = 1; for(int i = 2; i < MAX; i++){ fac[i] = fac[i-1] * i % MOD; inv[i] = MOD - inv[MOD%i] * (MOD/i) % MOD; finv[i] = finv[i-1] * inv[i] % MOD; } } long long COM(int n, int k){ if(n < k) return 0; if (n < 0 || k < 0) return 0; return fac[n] * (finv[k] * finv[n-k] % MOD) % MOD; } int H, W, N; vector<pint> wall; long long dp[3100][2] = {0}; int main() { COMinit(); cin >> H >> W >> N; wall.resize(N); for (int i = 0; i < N; ++i) { cin >> wall[i].first >> wall[i].second; --wall[i].first, --wall[i].second; } wall.push_back(pint(0, 0)); sort(wall.begin(), wall.end()); N = (int)wall.size(); dp[0][0] = 1; for (int i = 0; i < N; ++i) { for (int num = 0; num < 2; ++num) { for (int j = i + 1; j < N; ++j) { if (wall[j].first < wall[i].first || wall[j].second < wall[j].second) continue; int dx = wall[j].first - wall[i].first; int dy = wall[j].second - wall[i].second; long long factor = COM(dx + dy, dx); dp[j][1 - num] += dp[i][num] * factor % MOD; dp[j][1 - num] %= MOD; } } } long long res = COM(H+W-2, H-1); for (int i = 1; i < N; ++i) { for (int num = 0; num < 2; ++num) { int dx = H-1-wall[i].first; int dy = W-1-wall[i].second; long long tmp = dp[i][num] * COM(dx + dy, dx) % MOD; if (num & 1) res = (res - tmp + MOD) % MOD; else res = (res + tmp) % MOD; } } cout << res << endl; }