题解 P3824 【[NOI2017]泳池】

CYJian

2020-08-04 08:59:52

Solution

~~好像我的转移方法和大家都不同,那就顺便来发个题解吧...~~ 直接算最大面积为 $k$ 比较难处理,则考虑差分:先求出最大面积 $\le k$ 的概率,再减去最大面积 $\le k-1$ 的概率,就得到了最大面积 $=k$ 的概率了。 然后考虑设计状态 $f_{h, w}$ 为: 当前保证有一个高为 $h$,宽为 $w$ 的安全区域,且高为 $h+1$ 以上的位置任意且合法面积不超过 $k$ 的概率是多少。 注意到我们可以枚举高度为 $h+1$ 的那一行,左侧第一个危险的点出现的位置,则有转移: $$ f_{h, w} = p^w \times f_{h+1,w}+\sum_{i=1}^{w} f_{h+1,i-1} \times f_{h,w-i} \times p^{i-1}\times (1-p) $$ 然后最后的答案就是 $f_{0, n}$ 了。 乍一看,状态数 $O(n^2)$ 转移数 $O(n)$,复杂度就成了 $O(n^3)$。但是事实上针对最大面积不超过 $k$ 这个限制条件,我们真正有用的状态数是只有 $O(k \log k)$ 个的。则判断 $h \times w \le k$ 之后,实际复杂度是 $O(k^2 \log k)$ 的。 这个 `dp` 我是使用递归实现的,写起来很方便。当然也可以使用递推,但是需要注意循环的顺序($h$ 从大到小枚举,$w$ 从小到大枚举,这个不能乱顺序) 然后我们就解决了 $n, k \leq 10^3$ 的部分,到这里就可以拿到 $70$ 分了。 然后我们考虑一下 $n$ 很大的时候,$f_{0, n}$ 的转移。注意到对于所有 $x \ge k$,$f_{0, x}$ 都会用到 $f_{1,0}, f_{1,1},\ldots,f_{1,k}$ 进行转移。 注意到,如果我们先算出 $f_{1,0}, f_{1,1},\ldots,f_{1,k}$,那么就可以算出 $f_{0, x}$ 了。 考虑设 $G_i=f_{1,i}\times p^i \times q$,$F_n=f{0,n}$,则我们最后想知道的是 $F_n$,且 $F$ 可以通过 $G$ 互相转移: $$ F_n = \sum_{i=1}^{k+1} G_{i-1} \times F_{n-i} (n > k)$$ 使用矩阵乘法加速转移,结合上面的暴力 `dp` 就可以获得 $90$ 分的好成绩。 然后,如果你还会[常系数齐次线性递推](https://www.luogu.com.cn/problem/P4723)的话,你就会发现写出这个递推式之后就可以套上板子了。如果不会的话,建议在有一定数理基础的情况下再去学习。 由于此题中 $k \leq 10^3$,所以多项式乘法和取模可以暴力实现。 部分代码: ```cpp typedef long long ll; const int mod = 998244353; const ll MOD = 8ll * mod * mod; inline int Mod(int x) { return x >= mod ? x - mod : x; } inline void ADD(ll &x, ll y) { x += y, x -= x >= MOD ? MOD : 0; } inline int fsp(int x, int k = mod - 2) { int s = 1; while(k) { if(k & 1) s = 1LL * s * x % mod; x = 1LL * x * x % mod, k >>= 1; } return s; } int F[3010], lf; int Q[3010], lq; int S[3010], ls; int G[3010], lg; ll tmp[3010]; int A[3010]; inline void PolyMul(int a[], int b[], int c[], int &n, int m) { for(int i = 0; i <= n; i++) for(int j = 0; j <= m; j++) ADD(tmp[i + j], 1LL * b[i] * c[j]); n += m; for(int i = 0; i <= n; i++) a[i] = tmp[i] % mod, tmp[i] = 0; } inline void PolyMod(int a[], int &n) { for(int i = n; i >= lq; i--) { if(!a[i]) continue; int mul = mod - a[i]; for(int j = 0; j <= lq; j++) a[i - j] = (a[i - j] + 1LL * mul * Q[lq - j]) % mod; } chkmin(n, lq - 1); while(n && !a[n]) --n; } inline int calc(int n, int k) { memset(Q, 0, sizeof(Q)); memset(S, 0, sizeof(S)); memset(G, 0, sizeof(G)); Q[k] = 1, lq = k; for(int i = 0; i < k; i++) Q[k - i - 1] = mod - A[i]; S[0] = 1, ls = 0; G[1] = 1, lg = 1; while(n) { if(n & 1) PolyMul(S, S, G, ls, lg), PolyMod(S, ls); PolyMul(G, G, G, lg, lg), PolyMod(G, lg), n >>= 1; } int res = 0; for(int i = 0; i < k; i++) res = (res + 1LL * F[i] * S[i]) % mod; return res; } int p, q; int pw_p[1100000]; int pw_q[1100000]; int f[1010][1010]; inline int dp(int h, int w, int k) { if(w == 0) return 1; if(h * w > k) return 0; if(~f[h][w]) return f[h][w]; int &F = f[h][w] = 1LL * pw_p[w] * dp(h + 1, w, k) % mod; for(int i = 1; i <= w; i++) F = (F + 1LL * dp(h + 1, i - 1, k) * q % mod * pw_p[i - 1] % mod * dp(h, w - i, k)) % mod; return F; } inline int solve(int n, int k) { memset(f, -1, sizeof(f)); dp(0, k, k); memset(F, 0, sizeof(F)); memset(A, 0, sizeof(A)); for(int i = 0; i <= k; i++) { F[i] = i ? f[0][i] : 1; A[i] = !i ? q : 1LL * f[1][i] * q % mod * pw_p[i] % mod; } return calc(n, k + 1); } int main() { int n = ri, k = ri, x = ri, y = ri; p = 1LL * x * fsp(y) % mod, q = mod + 1 - p; int N = min(1000, max(n, k)) * 1001; pw_p[0] = pw_q[0] = 1; for(int i = 1; i <= N; i++) { pw_p[i] = 1LL * pw_p[i - 1] * p % mod; pw_q[i] = 1LL * pw_q[i - 1] * q % mod; } int r1 = solve(n, k); int r2 = solve(n, k - 1); printf("%d\n", Mod(r1 + mod - r2)); return 0; } ```