【数论+dp】codeforces559C Gerald and Giant Chess

传送门

题目描述:一个h*w( 1<=hw<=105 )的棋盘,有n( 1<=n<=2000 )个格子不能走。求从 1,1 hw 有多少条路径(对 109+7 取膜)。

做过马拦过河卒就知道用dp做。请注意数据范围!!
这道题之所以难,是因为数据大,用dp( Ohw )做会无限TLE(虽然时间上限是2s)。

然后身为蒟蒻的我就果断地放弃了……(毕竟是ACM制骗不了分的) /(ㄒoㄒ)/~
之后看了大神们的题解才做出来的。

采用补集的思想。
令dp[i]表示合法地走到第i个点的路径数。那么总路径数(不计合不合法)为 Cx1x+y2
不合法即经过了那些不能走的格子,这样的路径有 dp[j]Cxixjxi+yixjyj
dp[i]=Cxi1xi+yi2dp[j]Cxixjxi+yixjyj

在计算组合数时,现将阶乘以及它的逆元预处理出来。
计算逆元的方法有很多,在这里我用的是费马小定理 ap11 ( mod p),其中p是素数。

这样看来这道题不算太难,然而我放弃了……
蒟蒻加油 ↖(^ω^)↗

附代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#define LL long long int
#define MAXN 2050
#define MAXM 200050
#define mod 1000000007
using namespace std;

int h ,w ,n ;
LL dp[MAXN] ,fact[MAXM] ,ni[MAXM] ;

struct node
{
    int x ,y ;
    bool operator < (const node &a) const
    {
        return x+y<a.x+a.y;
    }
}point[MAXN] ;

LL power(LL a,LL pos)
{
    LL ans=1;
    while(pos>0)
    {
        if(pos&1)
            ans=ans*a%mod;
        a=a*a%mod;
        pos>>=1;
    }
    return ans;
}

void init()
{
    fact[0]=ni[0]=fact[1]=ni[1]=1;
    for(int i=2;i<=200000;++i)
    {
        fact[i]=fact[i-1]*i%mod;
        ni[i]=power(fact[i],mod-2);
    }
}

LL C(int a,int b)
{
    return fact[a]*ni[b]%mod*ni[a-b]%mod;
}

int main()
{
    init();
    while(~scanf("%d%d%d",&h,&w,&n))
    {
        for(int i=0;i<n;++i)
        {
            scanf("%d%d",&point[i].x,&point[i].y);
            --point[i].x ,--point[i].y;
        }
        point[n].x=h-1 ,point[n].y=w-1 ;
        sort(point,point+n+1);
        int x=point[0].x ,y=point[0].y ,u ,v ;
        dp[0]=C(x+y,x);
        for(int i=1;i<=n;++i)
        {
            x=point[i].x ,y=point[i].y ;
            dp[i]=C(x+y,x);
            for(int j=0;j<i;++j)
            {
                u=point[j].x ,v=point[j].y ;
                if(v>y||u>x)continue;
                dp[i]=(dp[i]-dp[j]*C(x+y-u-v,x-u)%mod)%mod;
                if(dp[i]<0)dp[i]+=mod;
            }
        }
        printf("%I64d\n",dp[n]);
    }
    return 0;
}

你可能感兴趣的:(dp,数论)