[BZOJ 4767]两双手(组合数学+Dp)

Description

老W是个棋艺高超的棋手,他最喜欢的棋子是马,更具体地,他更加喜欢马所行走的方式。老W下棋时觉得无聊,便
决定加强马所行走的方式,更具体地,他有两双手,其中一双手能让马从(u,v)移动到(u+Ax,v+Ay)而另一双手能让
马从(u,v)移动到(u+Bx,v+By)。小W看见老W的下棋方式,觉得非常有趣,他开始思考一个问题:假设棋盘是个无限
大的二维平面,一开始马在原点(0,0)上,若用老W的两种方式进行移动,他有多少种不同的移动方法到达点(Ex,Ey
)呢?两种移动方法不同当且仅当移动步数不同或某一步所到达的点不同。老W听了这个问题,觉得还不够有趣,他
在平面上又设立了n个禁止点,表示马不能走到这些点上,现在他们想知道,这种情况下马有多少种不同的移动方
法呢?答案数可能很大,你只要告诉他们答案模(10^9+7)的值就行。

Solution

Ax*By-Ay*Bx≠0所以向量A、B的步数是固定的,走法也就有C(m+n,m)种

先对障碍点排序

Dp,f[i]表示到达障碍点i且不经过其他障碍点的方案数(用容斥减去经过其他障碍点的方案数)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define Mod 1000000007
#define MAXN 500005
typedef long long LL;
using namespace std;
int n,ax,ay,bx,by;
LL fac[MAXN],inv[MAXN],f[MAXN];
struct Node
{
    int x,y;
}p[MAXN];
void init()
{
    fac[0]=1,inv[1]=1;
    for(int i=1;i<MAXN;i++)
    fac[i]=(fac[i-1]*i)%Mod;
    for(int i=2;i<MAXN;i++)
    inv[i]=((Mod-Mod/i)*inv[Mod%i])%Mod;
    inv[0]=1;
    for(int i=1;i<=MAXN;i++)
    inv[i]=(inv[i]*inv[i-1])%Mod;
}
LL C(LL x,LL y)
{
    if(x<y)return 0;
    return ((fac[x]*inv[y])%Mod*inv[x-y])%Mod;
}
bool cmp(Node A,Node B)
{
    if(A.x==B.x)return A.y<B.y;
    return A.x<B.x; 
}
void calc(int &x,int &y)
{
    int m,n;
    if((ay*x-ax*y)%(bx*ay-ax*by)){x=-1,y=-1;return;}
    n=(ay*x-ax*y)/(bx*ay-ax*by);
    if((by*x-bx*y)%(ax*by-bx*ay)){x=-1,y=-1;return;}
    m=(by*x-bx*y)/(ax*by-bx*ay);
    x=m,y=n;
}
int main()
{
    init();
    scanf("%d%d%d",&p[0].x,&p[0].y,&n);
    scanf("%d%d%d%d",&ax,&ay,&bx,&by);
    calc(p[0].x,p[0].y);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&p[i].x,&p[i].y);
        calc(p[i].x,p[i].y);
    }
    sort(p+1,p+1+n,cmp);
    p[n+1]=p[0];
    for(int i=1;i<=n+1;i++)
    {
        if(p[i].x==-1)continue; 
        f[i]=C(p[i].x+p[i].y,p[i].x);
        for(int j=1;j<i;j++)
        f[i]=(f[i]-f[j]*C(p[i].x-p[j].x+p[i].y-p[j].y,p[i].x-p[j].x)%Mod+Mod)%Mod;
    }
    printf("%lld\n",f[n+1]);
    return 0;
}

 

转载于:https://www.cnblogs.com/Zars19/p/6823051.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值