BZOJ4767-兩雙手-DP+容斥

傳送門

題意:

棋盤上的一個棋子,給出他的兩種移動方式:

1.(u,v)>(u+Ax,v+Ay)

2.(u,v)>(u+Bx,v+By)

現給出一些不能走的障礙點n個,求(0,0)到(Ex,Ey)的方案數

|Ax|,|Ay|,|Bx|,|By|<=500,0<=n,Ex,Ey<=500;AxByAyBx0

Solution:

因爲題目保證了AxByAyBx0 ,所以從原點出發走到某個點所需的兩種走法的次數是唯一的

具體可以列出方程,設到點(X,Y)分別需要a,b步

{Axa+Bxb=XAya+Byb=Y

我們只要把指定點的a,b算出來後,問題就轉化爲一個只能往右或者往上的常規路徑計數問題

但是(Ex,Ey)所對應的a,b可能很大,所以我們不能遞推地算

考慮容斥:

我們知道如果沒有障礙的話從(0,0)(n,m) 的方案數爲Cn+mn

f[i] 爲從原點出發到達第i個關鍵點的方案數,g[i][j] 表示從i到j的方案數

那麼我們可以得到:f[i]=g[0][i]j=1i1f[j]g[j][i]

顯然g數組就是一個組合數,預處理階乘以及階乘的逆元即可

複雜度O(n2)

代碼:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int mod=1e9+7;
const int N=1000000;
int n,m,t,cnt;
int f[510];
int a,b,c,d,mi[1000010],inv[1000010];
struct P{
    int x,y;
}p[510];
bool calc(int &x,int &y)
{
    int s1=x*d-y*c,t1=a*d-b*c,s2=x*b-y*a,t2=c*b-d*a;
    if (t1==0||t2==0) return 0;
    if (s1%t1) return 0;else x=s1/t1;
    if (s2%t2) return 0;else y=s2/t2;
    return 1; 
}
int fast_pow(int x,int a)
{
    int ans=1;
    for (;a;a>>=1,x=1ll*x*x%mod)
    {
        if (a&1) ans=1ll*ans*x%mod;
    }
    return ans;
}
bool cmp(P a,P b){if(a.x==b.x) return a.y<b.y; return a.x<b.x;}
int C(int n,int m){if (n<m) return 0;return 1ll*mi[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
    scanf("%d%d%d",&n,&m,&t);
    scanf("%d%d%d%d",&a,&b,&c,&d);
    if ((!calc(n,m))||n<0||m<0) {printf("0\n");return 0;}
    for (int x,y,i=1;i<=t;i++)
    {
        scanf("%d%d",&x,&y);
        if (calc(x,y)&&x>=0&&y>=0&&x<=n&&y<=m) p[++cnt]={x,y};
    }
    mi[0]=1;
    for (int i=1;i<=N;i++) mi[i]=1ll*mi[i-1]*i%mod;
    inv[N]=fast_pow(mi[N],mod-2);
    for (int i=N-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
    p[++cnt]={n,m};
    sort(p+1,p+1+cnt,cmp);
    f[0]=1;
    for (int i=1;i<=cnt;i++)
    {
        f[i]=C(p[i].x+p[i].y,p[i].x);
        for (int j=1;j<i;j++)
            f[i]=(1ll*f[i]+mod-(1ll*C(p[i].x-p[j].x+p[i].y-p[j].y,p[i].x-p[j].x)*f[j]%mod))%mod;
    }
    printf("%d",f[cnt]);
}
發佈了115 篇原創文章 · 獲贊 15 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章