CodeForces 559 C.Gerald and Giant Chess(组合数学+dp)

本文介绍了一种使用动态规划方法解决棋盘走法问题的技术,重点在于如何通过状态转移方程计算从起点到终点的合法路径数量。通过预处理减少计算复杂度,确保高效求解。实例演示了算法应用过程,并提供了源代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值