BZOJ4767 两双手

Link
首先我们可以列方程解出到某个点两种移动方式分别要走多少次。
那么这个就是经典的走路问题,答案为\(x+y\choose x\)
然后我们给所有的点排个序,设\(f_i\)表示不经过其它\([1,i)\)的点到达这个点的方案数。
转移的话容斥就行,枚举一个不经过的点\(j\),记从\(j\)\(i\)的方案数为\(s\)\(f_i-=s*f_j\)

#include<bits/stdc++.h>
#define pi pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=507,lim=500000,P=1000000007;
int read(){int x;cin>>x;return x;}
int dec(int a,int b){a-=b;return a<0? a+P:a;}
int mul(int a,int b){return 1ll*a*b%P;}
int inv(int a){int r=1,k=P-2;for(;k;k>>=1,a=mul(a,a))if(k&1)r=mul(a,r);return r;}
int ax,ay,bx,by,ex,ey,n,fac[lim+7],ifac[lim+7],f[N];pi a[N];
void cal(int &x,int &y)
{
    int a=x*by-y*bx,b=x*ay-y*ax,d=ax*by-ay*bx;
    if(!d||a/d*d!=a||b/d*d!=b) return (void)(x=y=-1);
    x=a/d,y=-b/d;
}
int C(int n,int m){return mul(mul(fac[n],ifac[m]),ifac[n-m]);}
int num(int x,int y){return x<0||0>y? 0:C(x+y,x);}
int main()
{
    int i,j,x,y;
    ex=read(),ey=read(),n=read(),ax=read(),ay=read(),bx=read(),by=read(),cal(ex,ey);
    for(i=1;i<=n;++i)
    {
	x=read(),y=read(),cal(x,y);
	if(x<0||x>ey||y<0||y>ey) --i,--n; else a[i]=pi(x,y);
    }
    a[++n]=pi(ex,ey),sort(a+1,a+n+1),fac[0]=1;
    for(i=1;i<=lim;++i) fac[i]=mul(fac[i-1],i);
    for(ifac[lim]=inv(fac[lim]),i=lim;i;--i) ifac[i-1]=mul(ifac[i],i);
    for(i=1;i<=n;++i) for(f[i]=num(a[i].fi,a[i].se),j=1;j<i;++j) f[i]=dec(f[i],mul(f[j],num(a[i].fi-a[j].fi,a[i].se-a[j].se)));
    cout<<f[n];
}
posted @ 2019-10-23 10:49  Shiina_Mashiro  阅读(130)  评论(0编辑  收藏  举报