【DP+组合数学】Codeforces559C[Gerald and Giant Chess]题解

题目概述

n m 列的过河卒问题,但有 k 个障碍物(不能经过),求方案数。

解题报告

哇,最近考到这道题,考试时完全不会做啊,考完后发现很水啊Orz。
朴素的障碍物过河卒问题直接刷递推,效率是 O(n×m) ,扛不住。而 k 比较小,显然要在障碍物上搞事情。
先将障碍物排序,定义 f[i] 表示前 i 个障碍物只过第 i 个障碍物的方案数。我们认为 (n,m) 是第 n+1 个障碍物,那么答案就是 f[n+1] 。问题是如何求出 f[i]
首先到第 i 个障碍物方案总数为 Cxi1xi+yi2 xi 表示第 i 个障碍物的横坐标, yi 表示第 i 个障碍物的纵坐标),而不满足的方案数为经过之前障碍物 j 后到达障碍物 i 的方案数 i1j=1{f[j]Cxixjxixj+yiyj|yjyi},两个相减就可以得到 f[i] 了。
由于这道题与大素数取模,所以我们预处理出所有阶乘的逆元就可以快速求出组合数,下面介绍一下线性的预处理:
1. 先用逆元公式 INV[i]=piINV[p mod i] 算出 i 的逆元。
2. 很显然 INV[i!]=ij=1INV[j]

示例程序

#include<cstdio>
#include<algorithm>
using namespace std;
#define fi first
#define se second
#define mp make_pair
typedef long long LL;
typedef pair<int,int> pii;
const int maxn=100000,maxm=100000,maxk=2000,MOD=1000000007;

int n,m,K,fac[maxn+maxm+5],INV[maxn+maxm+5],f[maxk+5];
pii a[maxk+5];

inline bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
inline char readc()
{
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; else return *l++;
}
inline int readi(int &x)
{
    int tot=0,f=1;char ch=readc(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=readc();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=readc();
    return x=tot*f,Eoln(ch);
}
void First_make()
{
    fac[0]=1;for (int i=1;i<=n+m;i++) fac[i]=(LL)fac[i-1]*i%MOD;
    INV[0]=INV[1]=1;for (int i=2;i<=n+m;i++) INV[i]=(LL)(MOD-MOD/i)*INV[MOD%i]%MOD;
    for (int i=1;i<=n+m;i++) INV[i]=(LL)INV[i-1]*INV[i]%MOD;
}
int C(int x,int y) {return (LL)fac[y]*INV[x]%MOD*INV[y-x]%MOD;}
int Sum(int i,int j) {return C(a[j].fi-a[i].fi,a[j].fi-a[i].fi+a[j].se-a[i].se);}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    readi(n);readi(m);readi(K);First_make();a[0]=mp(1,1);
    for (int i=1;i<=K;i++) readi(a[i].fi),readi(a[i].se);
    a[++K]=mp(n,m);sort(a+1,a+1+K);
    for (int i=1;i<=K;i++)
    {
        f[i]=Sum(0,i);
        for (int j=1;j<=i-1;j++) if (a[j].se<=a[i].se)
            f[i]=(f[i]-(LL)f[j]*Sum(j,i)%MOD+MOD)%MOD;
    }
    return printf("%d\n",f[K]),0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值