【BZOJ】4767: 两双手【组合数学】【容斥】【DP】

4767: 两双手

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 1057  Solved: 318
[Submit][Status][Discuss]

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

Solution

看起来很像一般的网格图求路径数,在没有障碍物的情况下,处理出到达终点分别用两种步数走的步数(解方程唯一固定,再用组合数学,方案数就等于$C(x+y,x)$,$x$和$y$分别是两种步数的次数。

可是这道题有障碍格。

所以在上述基础上进行容斥DP即可。将能走到的所有障碍点的$x$和$y$(从原点出发)预处理出来,(将终点的$x$、$y$值也放入结构体)排序后,后面的DP值要容斥减去前面能到达它的方案数。最后答案就是$dp[n]$。(因为一开始保证了结构体里所有障碍点都比目标点要小)

【注意】预处理阶乘的逆元线性从后往前线性求得,不然会TLE!

Code

#include<bits/stdc++.h>
#define mod 1000000007
#define LL long long
using namespace std;

int Ax, Ay, Bx, By, n, Ex, Ey;

struct Node {
    int x, y;
} QAQ[1005];
bool cmp(Node a, Node b) { if(a.x == b.x)    return a.y < b.y; return a.x < b.x; }

LL mpow(LL a, LL b) {
    LL ans = 1;
    for(; b; b >>= 1, a = a * a % mod)
        if(b & 1)    ans = ans * a % mod;
    return ans;
}

void cal(int &x, int &y) {////解方程计算两种步数
    LL a1, a2, b1, b2;
    b1 = y * Ax - x * Ay, b2 = Ax * By - Ay * Bx;
    a1 = x * By - y * Bx, a2 = Ax * By - Ay * Bx;
    if(a2 == 0 || b2 == 0) { x = -1, y = -1; return ; }
    if((a1 / a2) * a2 != a1 || (b1 / b2) * b2 != b1) { x = -1, y = -1; return ; }
    x = a1 / a2, y = b1 / b2;
}

LL fac[1000005], inv[1000005];
LL C(LL a, LL b) {
    if(a < b)    return 0;
    return fac[a] * inv[a-b] % mod * inv[b] % mod;
} 

void init() {
    fac[0] = 1;
    for(int i = 1; i <= 1000000; i ++)
        fac[i] = fac[i-1] * i % mod;
    inv[0] = 1; inv[1000000] = mpow(fac[1000000], mod - 2);
    for(int i = 999999; i >= 1; i --)
        inv[i] = inv[i + 1] * (i + 1) % mod;////线性求阶乘逆元
}

LL f[1005];
int main() {
    scanf("%d%d%d", &Ex, &Ey, &n);
    scanf("%d%d%d%d", &Ax, &Ay, &Bx, &By);
    cal(Ex, Ey);
    for(int i = 1; i <= n; i ++) {
        scanf("%d%d", &QAQ[i].x, &QAQ[i].y);
        cal(QAQ[i].x, QAQ[i].y);
        if(QAQ[i].x < 0 || QAQ[i].y < 0 || QAQ[i].x > Ex || QAQ[i].y > Ey) {/////不合法的步数筛掉
            n --; i --;
        }
    }
    QAQ[++n].x = Ex, QAQ[n].y = Ey;
    sort(QAQ + 1, QAQ + 1 + n, cmp);
    
    init();
    
    for(int i = 1; i <= n; i ++) {
        f[i] = C(QAQ[i].x + QAQ[i].y, QAQ[i].x);
        if(f[i] == 0)    continue;
        for(int j = 1; j < i; j ++) {
            f[i] -= (f[j] * C(QAQ[i].x - QAQ[j].x + QAQ[i].y - QAQ[j].y, QAQ[i].x - QAQ[j].x)) % mod;/////容斥
            f[i] = (f[i] % mod + mod) % mod;
        }
    }
    printf("%lld", f[n]);
    return 0;
}
posted @ 2018-10-14 16:00  Wans_ovo  阅读(182)  评论(0编辑  收藏  举报