Address
洛谷P3824
BZOJ4944
UOJ#316
LOJ#2304
Solution…
一、差分 容斥
要限制最大值恰好为一个定值往往是不好做的。
所以考虑容 (cha) 斥 (fen) ,把询问
拆成
和
,这样就转化成了出现过的每一个安全子矩形面积都不超过
(或不超过
)。
为了方便,下面我们只讨论每一个安全子矩形面积都不超过
的求法。
(求
和求
的方法大致相同,但是要特殊处理
和
的情况,否则在 UOJ 上会被 hack )
的答案为:
即最下面
个格子全部不安全。
的答案就不用解释了,为
。
二、 和
状态
的定义为底边长为
,高为
,最下面恰好
行全部安全并且这个底边长为
高为
的矩形内不存在任意安全子矩形面积大于
的概率。
表示底边长为
,高为
,最下面至少
行全部安全并且不存在任意安全子矩形面积大于
的概率。相当于是
的后缀和。
设
为第
列的最下面有多少个安全的格子。
那么显然第
列到第
列内的最大安全子矩形为
乘上
在区间
内的最小值。
这涉及到所有区间的最小值,故可以采用笛卡尔树的思想,找到最小值并向最小值的两边分治处理。
对于
,我们先枚举
表示
中最左的最小值在
位置。显然
应该为
(
的定义为最小值恰好为
)。
则
必须严格大于
即
(因为
是最左的最小值),
即
。
必须大于等于
,即
。
的概率就是下面
个格子全部安全而第
个格子不安全的概率。
即
。
得出转移:
是
的后缀和:
边界:
复杂度:看上去是
,但是任何一个
都必须满足
,所以
的上界只有
。
所以复杂度:
三、
的定义是底边长为
高为
,任意安全子矩形面积都不超过
的概率。
分两种情况:
(1)第
列最下面的格子不安全。
(2)第
列最下面的格子安全且产生的最大安全子矩形面积不超过
,而第
列的格子不安全。
得出转移:
边界
。
当然,如果
就不要转移。
这是一个常系数线性递推式。
我们可以先暴力计算出
,然后利用快速幂对特征多项式取模得到答案。
关于常系数线性递推可以右转:蒟蒻的 blog
复杂度
。
四、Other
注意到
的转移是卷积形式。
如果利用多项式求逆进行
的转移,并且利用 NTT 进行多项式取模,
那么复杂度可以优化到
。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 1007, M = N << 1, ZZQ = 998244353;
int n, K, p, q[N], f[N][N], g[N], h[M], a[M], tmp[M], s[N][N], st[N];
int qpow(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = 1ll * res * a % ZZQ;
a = 1ll * a * a % ZZQ;
b >>= 1;
}
return res;
}
void prod(int *a, int *b, int K)
{
int i, j;
memset(tmp, 0, sizeof(tmp));
For (i, 0, K) For (j, 0, K)
tmp[i + j] = (tmp[i + j] + 1ll * a[i] * b[j] % ZZQ) % ZZQ;
}
void xmod(int *a, int n, int K)
{
int i, j;
Rof (i, n, K + 1)
{
int tmp = a[i];
For (j, i - K - 1, i)
a[j] = (a[j] - 1ll * tmp * g[j - i + K + 1] % ZZQ + ZZQ) % ZZQ;
}
}
int jiejuediao(int K)
{
if (K == -1) return 0;
if (K == 0) return qpow((1 - p + ZZQ) % ZZQ, n);
int i, j, k;
q[0] = 1;
For (i, 1, K) q[i] = 1ll * q[i - 1] * p % ZZQ;
For (i, 0, K + 1) For (j, 0, K + 1) f[i][j] = s[i][j] = 0;
For (j, 0, K + 1) s[0][j] = 1;
For (i, 1, K) Rof (j, K / i, 0)
{
For (k, 0, i - 1) f[i][j] = (f[i][j] +
1ll * s[k][j + 1] * s[i - 1 - k][j] % ZZQ
* q[j] % ZZQ * (1 - p + ZZQ) % ZZQ) % ZZQ;
s[i][j] = (s[i][j + 1] + f[i][j]) % ZZQ;
}
g[K + 1] = 1;
For (i, 0, K)
g[i] = (ZZQ - 1ll * s[K - i][1] * (1 - p + ZZQ) % ZZQ) % ZZQ;
memset(a, 0, sizeof(a)); memset(h, 0, sizeof(h));
a[h[0] = 1] = 1;
int tm = n;
while (tm)
{
if (tm & 1)
{
prod(h, a, K);
For (i, 0, K << 1) h[i] = tmp[i];
xmod(h, K << 1, K);
}
prod(a, a, K);
For (i, 0, K << 1) a[i] = tmp[i];
xmod(a, K << 1, K);
tm >>= 1;
}
int res = 0;
st[0] = 1;
For (i, 1, K)
{
st[i] = s[i][1];
For (j, 0, i - 1)
st[i] = (st[i] + 1ll * st[j] * (1 - p + ZZQ) % ZZQ
* s[i - j - 1][1] % ZZQ) % ZZQ;
}
For (i, 0, K) res = (res + 1ll * h[i] * st[i] % ZZQ) % ZZQ;
return res;
}
int main()
{
n = read(); K = read();
int x = read(), y = read();
p = 1ll * x * qpow(y, ZZQ - 2) % ZZQ;
cout << (jiejuediao(K) - jiejuediao(K - 1) + ZZQ) % ZZQ << endl;
return 0;
}