题目链接:https://vjudge.net/problem/51Nod-1486
有一个h行w列的棋盘,里面有一些格子是不能走的,现在要求从左上角走到右下角的方案数。
单组测试数据。 第一行有三个整数h, w, n(1 ≤ h, w ≤ 10^5, 1 ≤ n ≤ 2000),表示棋盘的行和列,还有不能走的格子的数目。 接下来n行描述格子,第i行有两个整数ri, ci (1 ≤ ri ≤ h, 1 ≤ ci ≤ w),表示格子所在的行和列。 输入保证起点和终点不会有不能走的格子。
输出答案对1000000007取余的结果。
3 4 2 2 2 2 3
2
题意:
从(1,1)走到(h,w),只能往下或者往右,且不能走障碍点,问有多少条路线?
题解:
1.可知从(0,0)走到(n,m)有 C(n+m,n)条路径,那么对于一个位于(x,y)的障碍,从(1,1)走到它的位置有C(x+y-2,x-1)条路径。
2.设dp[i]为从(1,1)走到障碍i,且途中不经过任何障碍(除自己外)的路径数。那么怎么求dp[i]呢?
2.1 首先不考虑途中的障碍,那么就有C(x+y-2,x-1)条路径。
2.2 然后再考虑回途中的障碍,如果障碍j满足:x[j]<=x[i] && y[j]<=y[i],那么它就会存在于某些路径当中,即这些路径不合法,需要去除。为了把从(1,1)到障碍i所有不合法的路径删除掉,既不多删也不漏删,就需要一种合理的删除方式:对于一个满足x[j]<=x[i] && y[j]<=y[i] 的障碍j,我们删除以障碍j为路径上第一个障碍的路径,即 dp[j]*C(x[i]-x[j]+y[i]-y[j]-2, x[i]-x[j]-1),然后枚举所有满足条件的障碍j,就正好能够不多不少地把非法路径去除掉。
3. 为了操作方便,把右下角也当成是一个障碍,那么得到的dp[]即为答案。
学习之处:
1.求C[n][m]可以不用O(nm)的时间、空间去预处理,也可以不用O(m)的时间根据公式 C[n][m] = (n-m+1)*C[n][m-1]/m 进行递推。只需先用O(n)的时间预处理出阶乘A[],然后再利用公式:C[n][m] = A[n]/((n-m)!*m!) 以O(1)的时间复杂度求出。
2.按一定的规则或限定去枚举一个合法(非法)对象,并求出其对答案的影响,那么所有合法(非法)对象对答案的影响的并集,即为答案。
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <cmath> 7 #include <queue> 8 #include <stack> 9 #include <map> 10 #include <string> 11 #include <set> 12 using namespace std; 13 typedef long long LL; 14 const int INF = 2e9; 15 const LL LNF = 9e18; 16 const int MOD = 1e9+7; 17 const int MAXN = 2e3+10; 18 19 struct node 20 { 21 int x, y; 22 bool operator<(const node &a){ 23 if(x==a.x) return y<a.y; 24 return x<a.x; 25 } 26 }a[MAXN]; 27 28 LL qpow(LL x, LL y) 29 { 30 LL s = 1; 31 while(y) 32 { 33 if(y&1) s = (s*x)%MOD; 34 x = (x*x)%MOD; 35 y >>= 1; 36 } 37 return s; 38 } 39 40 LL A[200010], inv[200010]; //预处理出阶乘A[i],以及逆元inv[i],其中inv[i]是A[i]的逆元。 41 LL C(int n, int m) 42 { 43 return (((A[n]*inv[n-m])%MOD)*inv[m])%MOD; 44 } 45 46 LL dp[MAXN]; 47 int main() 48 { 49 int h, w, n; 50 A[0] = inv[0] = 1; 51 for(int i = 1; i<200010; i++) //预处理 52 { 53 A[i] = (i*A[i-1])%MOD; 54 inv[i] = qpow(A[i], MOD-2); 55 } 56 while(scanf("%d%d%d", &h,&w,&n)!=EOF) 57 { 58 for(int i = 1; i<=n; i++) 59 scanf("%d%d", &a[i].x, &a[i].y); 60 a[++n].x = h; a[n].y = w; //把右下角加进去,简化处理 61 sort(a+1,a+1+n); //按坐标排序 62 for(int i = 1; i<=n; i++) 63 { 64 dp[i] = C(a[i].x+a[i].y-2, a[i].x-1); //初始化 65 for(int j = 1; j<i; j++) //去除不合法的 66 if(a[i].x>=a[j].x&&a[i].y>=a[j].y) 67 dp[i] = (dp[i]-(dp[j]*C(a[i].x-a[j].x+a[i].y-a[j].y, a[i].x-a[j].x))%MOD+MOD)%MOD; 68 } 69 printf("%lld ", dp[n]); 70 } 71 }