bzoj4767 两双手

http://www.elijahqi.win/archives/3596
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)的值就行。

Input
第一行三个整数Ex,Ey,n分别表示马的目标点坐标与禁止点数目。
第二行四个整数Ax,Ay,Bx,By分别表示两种单步移动的方法,保证Ax*By-Ay*Bx≠0
接下来n行每行两个整数Sxi,Syi,表示一个禁止点。
|Ax|,|Ay|,|Bx|,|By| <= 500, 0 <= n,Ex,Ey <= 500

Output
仅一行一个整数,表示所求的答案。

Sample Input
4 4 1
0 1 1 0
2 3
Sample Output
40
HINT

Source
因为题目有特殊限制 所以列出方程发现我们可以唯一的用几步1走法+几步2走法 走到一个位置

那么就把这个几步当做 向量来看即可 这就变成了 每次只能往上往右走的dp套路题

注意最终的位置可能算出的向量并非最大 所以不能提前算出 一起排序

#include<cstdio>
#include<cctype>
#include<algorithm>
#define ll long long
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(!isdigit(ch)) {if (ch=='-') f=-1;ch=gc();}
    while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
    return x*f;
}
const int mod=1e9+7;
const int N=5e5+10;
struct node{
    int x,y;
}p[550];
int jc[N],inv[N],n,ex,ey,ax,ay,bx,by,dp[550];
inline void dec(int &x,int v){x=x-v<0?x-v+mod:x-v;}
inline int ksm(ll b,int t){static ll tmp;
    for (tmp=1;t;b=b*b%mod,t>>=1) if(t&1) tmp=tmp*b%mod;return tmp;
}
inline bool cmp(const node &a,const node &b){
    return a.x==b.x?a.y<b.y:a.x<b.x;
}
inline void calc(int &A,int &B){
    static int x,y;
    if ((A*ay-B*ax)%(bx*ay-ax*by)){A=-1;B=-1;return;}
    else y=(A*ay-B*ax)/(bx*ay-ax*by);
    if ((A*by-B*bx)%(ax*by-ay*bx)){A=-1;B=-1;return;}
    else x=(A*by-B*bx)/(ax*by-ay*bx);
    A=x;B=y;    
}
inline void init(){
    jc[0]=1;
    for (int i=1;i<=5e5;++i) jc[i]=(ll)jc[i-1]*i%mod;
    inv[500000]=ksm(jc[500000],mod-2);
    for (int i=5e5-1;~i;--i) inv[i]=(ll)inv[i+1]*(i+1)%mod;
}
inline int C(int n,int m){
    if (n<m) return 0;
    return (ll)jc[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
    freopen("bzoj4767.in","r",stdin);
    ex=read();ey=read();n=read();init();
    ax=read();ay=read();bx=read();by=read();
    for (int i=1;i<=n;++i){
        p[i].x=read();p[i].y=read();calc(p[i].x,p[i].y);
    }
    sort(p+1,p+n+1,cmp);
    p[++n].x=ex;p[n].y=ey;calc(p[n].x,p[n].y);
    for (int i=1;i<=n;++i){
        if (p[i].x==-1) continue;
        dp[i]=C(p[i].x+p[i].y,p[i].x);
        for (int j=1;j<i;++j)
            if (p[i].y>=p[j].y) dec(dp[i],(ll)dp[j]*C(p[i].x-p[j].x+p[i].y-p[j].y,p[i].x-p[j].x)%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、付费专栏及课程。

余额充值