Codeforces #313 (Div. 1) C. Gerald and Giant Chess dp 组合数 逆元

描述

有一个 h×w 的棋盘,需要从左上角走到右下角。每次只能向右或者向下走一步。其中有 n 个格子被标记成不能就经过,这些格子的坐标为(ri,ci)。问走到右下角可以有多少种方案数。
(1h,w105,1n2000,1rij,1ciw,ansmod109+7)

思路

一开始的思路是容斥原理,但是公式推了半天都推不出来。最后经过指导得出了解法。
记从 (x1,y1) (y1,y2) 的方案数为 f(x1,y1,x2,y2)=c(x2x1+y2y1,y2y1)
dp(x,y) 为到从起点到 (x,y) 点并且不经过被标记的点的方案数。
直接算 dp(x,y) 确实不好算,我们可以算出总共的,再减去不合法的。总共的值为 f(1,1,x,y) ,对于 (1,1) (x,y) 之间的所有的被标记的点 (ri,ci) 那么一它为第一个经过的被标记的点的方案数是 dp(ri,ci)×f(ri,ci,x,y)

那么

dp(x,y)=f(1,1,x,y)(dp(ri,ci)×f(ri,ci,x,y))

于是我们要计算的是 dp(h,w) ;


code

#include <bits/stdc++.h>
using namespace std;

const int MOD = 1E9+7;
const int maxn=300005;
long long fac[maxn];
long long inv[maxn];

/*=================================================*/
long long power(long long a,long long p) {  
    long long res=1;  
    while(p) {  
        if(p&1)  
            res=(res*a)%MOD;  
        a=(a*a)%MOD;  
        p>>=1;  
    }  
    return res;  
}  

long long Inv(long long a) {  
    return power(a,MOD-2);  
}  

void init() {  
    fac[0]=inv[0]=1;  
    for(long long i=1;i<300005;i++) {  
        fac[i]=fac[i-1]*i;  
        fac[i]%=MOD;  
        inv[i]=Inv(fac[i]);  
    }  
}  

long long C(long long n,long long m) {  
    if(n<0||m<0)  
        return 0;  
    long long res=fac[n];
    res%=MOD;  
    res*=inv[m];
    res%=MOD;
    res*=inv[n-m];
    res%=MOD;
    return res;
}  
/*=================================================*/

struct Node {
    int x, y;
    Node() {}
    Node(int _x, int _y) {
        x = _x;
        y = _y;
    }
}p[2005];

bool cmp (Node a, Node b) {
    if (a.x == b.x) return a.y < b.y;
    else return a.x < b.x;
}

int h, w, n;
long long res = 0;
long long dp[2005];

int main () {
    init();

    scanf("%d%d%d", &h, &w, &n);

    p[0] = Node(h, w);
    for (int i=1; i<=n; i++) {
        scanf ("%d%d", &p[i].x, &p[i].y);
    }

    sort(p, p+n+1, cmp);

    dp[0] = C(p[0].x + p[0].y - 2, p[0].x-1);

    for (int i=1; i<=n; i++) {
        dp[i] = C(p[i].x - 1 + p[i].y - 1, p[i].x - 1);

        res = 0;
        for (int j=0; j<i; j++) {
            if (p[j].x <= p[i].x && p[j].y <= p[i].y) {
                res = (res + (dp[j] * C(p[i].x - p[j].x + p[i].y - p[j].y, p[i].x - p[j].x)) % MOD ) % MOD;
            }
        }
        dp[i] = (dp[i] - res + MOD) % MOD;
    }

    printf("%d\n", dp[n]);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值