codeforces 560E dp+组合数学

本文介绍了一种在二维坐标系中,从起点(1,1)到终点(h,w),避开坏点,只允许向上、向右或斜向上的路径计数算法。通过动态规划和组合数学方法,计算出所有可能路径的数量。

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

题意

在一个二维坐标系中,起点在(1,1)(1,1)(1,1),终点在(h,w)(h,w)(h,w),期间只能向上,向右,斜向上
并且不能经过坏点的方案数。坏点有n个

分析

我们分析两个坏点之间的方案数:Cxi−xj+yi−yjxi−xjC_{x_i-x_j+y_i-y_j}^{x_i-x_j}Cxixj+yiyjxixj
我们定义dp[i]dp[i]dp[i]为从(1,1)(1,1)(1,1)点到第iii个坏点之间不经过坏点的方案数
那我们知道dp[0]=Cx0−1+y0−1x0−1dp[0]=C_{x_0-1+y_0-1}^{x_0-1}dp[0]=Cx01+y01x01

dp[i]=Cxi−1+yi−1xi−1+∑j=1i−1dp[j]∗Cxi−xj+yi−yjxi−xjdp[i]=C_{x_i-1+y_i-1}^{x_i-1}+\sum_{j=1}^{i-1}dp[j]*C_{x_i-x_j+y_i-y_j}^{x_i-x_j}dp[i]=Cxi1+yi1xi1+j=1i1dp[j]Cxixj+yiyjxixj

我们对所有的坏点进行排序(点(h,w)(h,w)(h,w)也算一个坏点),然后跑到最后一个坏点的dp值即为答案

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T>
void out(T x) { cout << x << endl; }
ll fast_pow(ll a, ll b, ll p) {ll c = 1; while(b) { if(b & 1) c = c * a % p; a = a * a % p; b >>= 1;} return c;}
ll exgcd(ll a, ll b, ll &x, ll &y) { if(!b) {x = 1; y = 0; return a; } ll gcd = exgcd(b, a % b, y, x); y-= a / b * x; return gcd; }
const int N = 2e3 + 5;
const int NN = 2e5 + 10;
const int mod = 1e9 + 7;
struct node 
{
    int x, y;
    bool operator < (const node b) const 
    {
        if(x == b.x)
            return y < b.y;
        return x < b.x;
    }
}pp[N];
ll fac[NN], inv[NN];
void init()
{
    inv[0] = fac[0] = 1;
    for(int i = 1; i < NN; i ++)
        fac[i] = fac[i - 1] * i % mod;
    inv[NN - 1] = fast_pow(fac[NN - 1], mod - 2, mod);
    for(int i = NN - 2; i >= 1; i --)
        inv[i] = inv[i + 1] * (i + 1) % mod;
}
ll C(int n, int m)
{ 
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
ll dp[N];
int main()
{
    ios::sync_with_stdio(false);
    init();
    int h, w, n;
    cin >> h >> w >> n;
    for(int i = 0; i < n; i ++)
        cin >> pp[i].x >> pp[i].y;
    pp[n].x = h;
    pp[n].y = w;
    n ++;
    sort(pp, pp + n);
    dp[0] = C(pp[0].x + pp[0].y - 2, pp[0].x - 1);
    for(int i = 1; i <= n; i ++)
    {
        dp[i] = C(pp[i].x + pp[i].y -2, pp[i].x - 1);
        for(int j = 0; j < i; j ++)
        {
            if(pp[i].x >= pp[j].x && pp[i].y >= pp[j].y)//只能向上,向下或者斜上
                dp[i] = (dp[i] - dp[j] * C(pp[i].x - pp[j].x + pp[i].y - pp[j].y, pp[i].x - pp[j].x) % mod + mod) % mod;
        }
    }
    cout << dp[n - 1] << endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值