bzoj 4767 两双手 - 动态规划 - 容斥原理
Posted yyf0309
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj 4767 两双手 - 动态规划 - 容斥原理相关的知识,希望对你有一定的参考价值。
题目传送门
题目大意
一个无限大的棋盘上有一只马,设马在某个时刻的位置为$(x, y)$, 每次移动可以将马移动到$(x + A_x, y + A_y)$或者$(x + B_x, y + B_y)$。棋盘上有$n$个禁止位置不能经过,问马从$(0, 0)$走到$(E_x, E_y)$的方案数。
容斥是显然的。
每确定经过$k$个禁止位置的方案数的容斥系数是$(-1)^{k}$。
考虑带上容斥系数来动态规划,
注意到去掉重复的禁止位置后,$(0, 0), (E_x, E_y)$以及禁止位置构成了一个DAG。
容斥相当于求从$(0, 0)$到$(E_x, E_y)$的经过偶数个禁止位置的路径数减去经过奇数个禁止位置的路径数。
直接动态规划计数就好了。
Code
1 /** 2 * bzoj 3 * Problem#4767 4 * Accepted 5 * Time: 333ms 6 * Memory: 10716k 7 */ 8 #include <iostream> 9 #include <cassert> 10 #include <cstdlib> 11 #include <cstdio> 12 #include <queue> 13 #include <set> 14 using namespace std; 15 typedef bool boolean; 16 17 const int N = 505, M = 1e9 + 7, D = 1e6 + 1; 18 19 int add(int a, int b) { 20 return ((a += b) >= M) ? (a - M) : (a); 21 } 22 23 int sub(int a, int b) { 24 return ((a -= b) < 0) ? (a + M) : (a); 25 } 26 27 int mul(int a, int b) { 28 return a * 1ll * b % M; 29 } 30 31 #define pii pair<int, int> 32 #define fi first 33 #define sc second 34 35 void exgcd(int a, int b, int& x, int& y) { 36 if (!b) 37 x = 1, y = 0; 38 else { 39 exgcd(b, a % b, y, x); 40 y -= (a / b) * x; 41 } 42 } 43 44 int inv(int a, int n) { 45 int x, y; 46 exgcd(a, n, x, y); 47 return (x < 0) ? (x + n) : (x); 48 } 49 50 int fac[D], _fac[D]; 51 52 inline void prepare() { 53 fac[0] = 1; 54 for (int i = 1; i < D; i++) 55 fac[i] = mul(fac[i - 1], i); 56 _fac[D - 1] = inv(fac[D - 1], M); 57 for (int i = D; --i; ) 58 _fac[i - 1] = mul(_fac[i], i); 59 } 60 61 int comb(int n, int m) { 62 if (n < m) 63 return 0; 64 return mul(fac[n], mul(_fac[n - m], _fac[m])); 65 } 66 67 boolean Solve(int a1, int b1, int a2, int b2, int c1, int c2, pii& sol) { 68 int d = c2 * a1 - a2 * c1, f = a1 * b2 - a2 * b1; 69 if (d % f) 70 return false; 71 sol.sc = d / f; 72 if (a1) { 73 d = c1 - b1 * sol.sc; 74 if (d % a1) 75 return false; 76 sol.fi = d / a1; 77 } else { 78 d = c2 - b2 * sol.sc; 79 if (d % a2) 80 return false; 81 sol.fi = d / a2; 82 } 83 return sol.fi >= 0 && sol.sc >= 0; 84 } 85 86 int n, Ex, Ey; 87 int Ax, Ay, Bx, By; 88 pii ps[N]; 89 int deg[N]; 90 set<pii> s; 91 boolean g[N][N]; 92 93 //#define _DEBUG_ 94 #ifdef _DEBUG_ 95 FILE* fin = fopen("6.in", "r"); 96 #else 97 FILE* fin = stdin; 98 #endif 99 100 boolean Solve(pii ps, pii pt, pii& sol) { 101 return Solve(Ax, Bx, Ay, By, pt.fi - ps.fi, pt.sc - ps.sc, sol); 102 } 103 104 inline void init() { 105 fscanf(fin, "%d%d%d", &Ex, &Ey, &n); 106 fscanf(fin, "%d%d%d%d", &Ax, &Ay, &Bx, &By); 107 for (int i = 1; i <= n; i++) { 108 fscanf(fin, "%d%d", &ps[i].fi, &ps[i].sc); 109 if (s.count(ps[i])) 110 i--, n--; 111 s.insert(ps[i]); 112 } 113 } 114 115 int f[N]; 116 queue<int> que; 117 inline void solve() { 118 int t = n + 1; 119 pii p, S(0, 0), T(Ex, Ey); 120 ps[0] = S, ps[t] = T; 121 for (int i = 1; i <= n; i++) 122 if (Solve(S, ps[i], p)) 123 g[0][i] = true, deg[i]++; 124 for (int i = 1; i <= n; i++) 125 for (int j = 1; j <= n; j++) 126 if ((i ^ j) && Solve(ps[i], ps[j], p)) 127 g[i][j] = true, deg[j]++, assert(!g[j][i]); 128 for (int i = 1; i <= n; i++) 129 if (Solve(ps[i], T, p)) 130 g[i][t] = true, deg[t]++; 131 if (Solve(S, T, p)) 132 g[0][t] = true, deg[t]++; 133 134 f[0] = 1; 135 que.push(0); 136 while (!que.empty()) { 137 int e = que.front(); 138 que.pop(); 139 for (int i = 1; i <= t; i++) { 140 if (!g[e][i]) 141 continue; 142 Solve(ps[e], ps[i], p); 143 if (i && i != t) 144 f[i] = sub(f[i], mul(f[e], comb(p.fi + p.sc, p.fi))); 145 else 146 f[i] = add(f[i], mul(f[e], comb(p.fi + p.sc, p.fi))); 147 if (!(--deg[i])) 148 que.push(i); 149 } 150 } 151 // for (int i = 1; i <= n; i++) 152 // assert(!deg[i]); 153 // if (deg[i]) 154 // cerr << i << " " << ps[i].fi << " " << ps[i].sc << endl; 155 printf("%d ", f[t]); 156 } 157 158 int main() { 159 prepare(); 160 init(); 161 solve(); 162 return 0; 163 }
!-- .mycontent>
以上是关于bzoj 4767 两双手 - 动态规划 - 容斥原理的主要内容,如果未能解决你的问题,请参考以下文章