Description
有个$H$行$W$列的棋盘,里面有$N$个黑色格子,求一个棋子由左上方格子走到右下方格子且不经过黑色格子的方案数.
$1<=H,M<=1e5,1<=N<=2000$.输出对$1e9+7$去模后的结果即可
Sol
假设没有黑色格子,方案数就为$C_{H+W-2}^{H-1}$.
简单说下,可以把向下走看做$0$,向右走看做$1$,其实就是求$01$序列的种数
注意到,黑色格子的总数相当少,所以我们可以把求不经过黑色格子的方案数转化成总方案数减去至少经过一个黑色格子的方案数
在求解计数类$DP$问题时,通常要找到一个"基准点",围绕这个基准点构造一个不可划分的"整体",以避免子问题的重叠.
这句话的意思大概就是找到标准把问题划分为子问题,且这些子问题具有互斥性.
将统计至少经过一个黑色格子的问题转化为枚举每一个黑色格子,并且将以该黑色格子为第一个经过的黑色格子的路径方案数相加.(定语似乎太长了,但是应该都懂的叭)
具体来说,将所有的黑色格子按照行,列坐标递增的顺序排序.第$i$个黑色格子在第$xi$行,$yi$列,设$F[i]$表示从左上角走到第$i$个黑色格子,并且途中不经过其他黑色格子的路线数.
$F[i]=C_{xi-1+yi-1}^{xi-1}-sum_{j=0}^{i-1}F[j]*C_{xi-xj+yi-yj}^{xi-xj}$,其中$xi>=xj,yi>=yj$
特别地,我们假设左上角的格子为第$0$个黑色格子,右下角的格子为第$N+1$个黑色格子.以$F[0]=1$为初值,$F[n+1]$为答案.
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<algorithm> #define il inline #define Rg register #define go(i,a,b) for(Rg int i=a;i<=b;i++) #define yes(i,a,b) for(Rg int i=a;i>=b;i++) #define ll long long using namespace std; il int read() { int x=0,y=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*y; } const int N=2010,M=200010; int h,w,n,mod=(1e9)+7; ll f[N],jc[M],inv[M]; struct node{int x,y,s;}a[N]; il bool cmp(node x,node y){if(x.x==y.x)return x.y<y.y;return x.x<y.x;} il ll ksm(ll x,int y) { ll s=1; while(y){if(y&1)s=(s*x)%mod;x=(x*x)%mod;y>>=1;} return s; } il void init_C() { jc[0]=1,inv[0]=1; go(i,1,200000) jc[i]=jc[i-1]*i%mod,inv[i]=ksm(jc[i],mod-2); } il ll C(int x,int y) { return jc[x]*inv[y]%mod*inv[x-y]%mod; } int main() { h=read(),w=read(),n=read();init_C(); go(i,1,n){a[i]=(node){read(),read()},a[i].s=a[i].x+a[i].y;} a[n+1]=(node){h,w,h+w}; sort(a+1,a+n+1,cmp); go(i,1,n+1) { f[i]=C(a[i].s-2,a[i].x-1); go(j,1,i-1) { if(a[j].x>a[i].x || a[j].y>a[i].y)continue; f[i]=(f[i]-f[j]*C(a[i].s-a[j].s,a[i].x-a[j].x))%mod; } } printf("%lld ",(f[n+1]+mod)%mod); return 0; }