题目链接:https://www.51nod.com/Challenge/Problem.html#problemId=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
题解:
首先h*w的网格,如果从(1,1)走到(h,w)需要向下走h-1补,向右走w-1步,总共走h-1+w-1步
那么方案数就是C(h-1+w-1,w-1) 。
可以先预处理把所有的位置的横纵坐标x,y都减一,算出每个点从起点分别要向下向右走多少步。
算出总数,减去不合法的路径。
对于一个不合法的(x,y),经过这个点的不合法方案数应该是C(x+y,y)*C(h-x+w-y,w-y).
算不合法路径的时候可能造成某一条路径多次计算,需要容斥。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int N=2005;
const int mod=1e9+7;
ll fac[maxn],ni[maxn];
ll dp[N];
pair<ll,ll> p[N];
ll h,w,n;
ll ksm(ll a,ll b){
ll ans=1;
while(b){
if(b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
ll C(ll n,ll m){
if(n<0||m<0||m>n) return 0;
return fac[n]*ni[m]%mod*ni[n-m]%mod;
}
void init(){
fac[0]=1;
for(int i=1;i<maxn;i++) fac[i]=fac[i-1]*i%mod;
ni[maxn-1]=ksm(fac[maxn-1],mod-2);
for(int i=maxn-2;i>=0;i--) ni[i]=ni[i+1]*(i+1)%mod; //递推求阶乘的逆元
}
int main(){
init();
scanf("%lld%lld%lld",&h,&w,&n);
h--,w--;
for(int i=0;i<n;i++){
scanf("%lld%lld",&p[i].first,&p[i].second);
p[i].first--;p[i].second--;
}
sort(p,p+n);
ll ans=C(h+w,w);
for(int i=0;i<n;i++){
ll a=p[i].first;
ll b=p[i].second;
dp[i]=C(a+b,a);
for(int j=0;j<i;j++){
ll aa=p[j].first;
ll bb=p[j].second;
ll tem=C(a-aa+b-bb,b-bb);
dp[i]=(dp[i]-dp[j]*tem%mod+mod)%mod;
}
ans=(ans-dp[i]*C(h-a+w-b,w-b)%mod+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}