Description
一块h*w的棋盘,上面有n个黑块,现在处于左上角的棋子只能往下或往右走,且不能经过黑块,问走到终点的路径有多少种
Input
第一行三个整数h,w和n,之后n行每行两个整数x和y表示该黑块坐标
Output
输出到达终点的路径条数,结果模1e9+7
Sample Input
3 4 2
2 2
2 3
Sample Output
2
Solution
因为此题h和w数据量过大,搜索显然不行,因黑块最多2000个,所以考虑以黑块为突破点利用dp求解路径条数,设dp[i]为起点到第i个黑块的合法路径数,num[i][j]为第i个黑块到第j个黑块的所有路径,设起点为第0个黑块,终点为第n+1和黑块,那么有以下转移方程dp[i]=num[0][i]-sum{ dp[j]*num[i][j] },j=1,2,…,i-1,dp[n+1]即为答案
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 200005
typedef long long ll;
const long long p=1000000007ll;
ll rev[maxn];
ll power[maxn];
ll dp[2222];
struct node
{
int x;
int y;
}pot[2222];
ll mod_pow(ll a,ll b,ll m)//快速幂
{
ll ans=1;
a%=m;
while(b)
{
if(b&1)ans=ans*a%m;
b>>=1;
a=a*a%m;
}
return ans;
}
void init()//预处理出n的阶乘及其阶乘的逆元
{
power[0]=1;
for(int i=1;i<maxn;i++)
power[i]=power[i-1]%p*i%p;
rev[1]=1;
for(int i=0;i<maxn;i++)
rev[i]=mod_pow(power[i],p-2,p);
}
ll f(int n,int m)//求组合数
{
if(m==0||n-m==0)
return 1;
return (ll)power[n]*rev[m]%p*rev[n-m]%p;
}
int cmp(node a,node b)//排序函数
{
if(a.x!=b.x)//先排横坐标
return a.x<b.x;
return a.y<b.y;//横坐标相同则排纵坐标
}
ll solve(int x1,int y1,int x2,int y2)//求(x1,y1)到(x2,y2)的所有路径数
{
int x=x2-x1+1;
int y=y2-y1+1;
return f(x+y-2,x-1);
}
int main()
{
init();
int h,w,n;
cin>>h>>w>>n;
for(int i=1;i<=n;i++)
scanf("%d%d",&pot[i].x,&pot[i].y);
pot[n+1].x=h;//将终点也看作一个黑块
pot[n+1].y=w;
n++;
sort(pot+1,pot+n+1,cmp);//对黑块位置排序
for(int i=1;i<=n;i++)//预处理出起点到达某黑块的所有路径数
dp[i]=solve(1,1,pot[i].x,pot[i].y);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
if(pot[j].y<pot[i].y)
continue;
dp[j]-=(dp[i]*solve(pot[i].x,pot[i].y,pot[j].x,pot[j].y))%p;
if(dp[j]<0)dp[j]+=p;
}
printf("%d\n",dp[n]);
return 0;
}