传送门:https://codeforces.com/problemset/problem/559/C
大意:给定一个n * m 的棋盘,有 k 个障碍点,障碍点不能走,求从(1,1)走到(n,m)(只能往下或往右走)的方案数。
(
1
<
=
n
,
m
<
=
1
e
5
,
k
<
=
2000
)
(1<=n,m<=1e5,k<=2000)
(1<=n,m<=1e5,k<=2000)
思路:棋盘非常大,但是我们看到障碍点并不多。所以我们可以考虑从障碍点的角度入手。如果没有障碍点,从(1,1)到(n,m)的方案数可以通过组合数直接计算,即为
C
(
n
+
m
−
2
,
n
−
1
)
C(n+m-2,n-1)
C(n+m−2,n−1)。接下来就是考虑含有障碍点的情况。我们将所有的障碍点按行、列从上到下、从左到右排序,对于障碍点0、1、2…k-1。我们令f[i] 表示避开0~ i -1号障碍点到达 i 点的方案数,我们把终点也看成障碍点的话那么最终结果显然就是f[k]。接下来就是怎么就行状态转移?
对于求 f[i] 的时候我们显然可以先不考虑障碍点,总的方案数很容易求,然后减去所有经过某个障碍点到 i 的方案数就行了。这时候我们dp的优势就体现出来了,我们直接求的话还要考虑容斥去掉经过多个障碍点到 i 的方案,但是根据我们定义的状态进行计算的话,是只通过某一个障碍点到 i 的方案,显然不会重复。代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2010, M = 1e9 + 7 ;
struct node {
ll x, y;
bool operator <(const node &W)const
{
if(x != W.x)return x < W.x;
return y < W.y;
}
}a[N];
ll n, m, k, f[N];
ll fac[N*100],inv[N*100];
ll qmi(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)res=res*a%M;
a=a*a%M;
b>>=1;
}
return res%M;
}
ll C(ll a,ll b)
{
if(a<b)return 0;
return fac[a]*inv[b]%M*inv[a-b]%M;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
inv[0]=fac[0]=1;
for(int i=1;i<=2e5;i++)
{
fac[i]=fac[i-1]*i%M;
inv[i]=inv[i-1]*qmi(i,M-2)%M;
}
cin >> n >> m >> k;
for(int i=0; i < k; i++)
{
int x, y;
cin >> x >> y;
a[i] = {x, y};
}
sort(a,a+k);
a[k] = {n, m};
for(int i=0;i<=k;i++)
{
ll x=a[i].x,y=a[i].y;
f[i]=C(x+y-2,x-1);
ll t=0;
for(int j=0;j<i;j++)
{
ll xx=a[j].x,yy=a[j].y;
t+=f[j]*C(x-xx+y-yy,x-xx)%M;
t%=M;
}
f[i]=((f[i]-t)%M+M)%M;
}
cout<<f[k]<<"\n";
return 0;
}