前置知识
- 高斯消元
- 快速幂
Description
共有 (m + 1) 个数,第一个初始值为 (p),必须在 ([0, n]) 的区间修改,剩下 (m) 个都是无穷。
每轮操作是:
- 不为最大值的数中等概率随机选择一个把它 (+1)
- (k) 次找一个不为最小值的数把它 (-1)
如果不存在就不操作,现在问期望进行多少次操作,第一个数会变成最小值 (0)。
Solution
对于这种数学期望的题,比较套路的就是设状态,根据是否有后效性决定高斯消元/DP,这个状态倒着推最好是当前到终点的期望,否则如果正着推,不太好做。
发现 (m) 个无穷的数和目标和答案均无关,可以当做一个出气筒(?。所以对于任意一种状态,我们只需要记录第一个数是什么就可以了。
设置状态
设 (E_i) 为第一个数为(i) 这个状态走到终点的的期望步数。
已知:(E_0 = 0)
目标:(E_{p})
状态转移
一些预处理
考虑在不同的血量情况下,一轮操作可能带来加减多少的概率是一定的,不妨先把这个搞出来。
为了简化公式,可以先预处理一个 (H_i) 表示在一轮的 (k) 次操作中,受到 (i) 的伤害(让这个数 (-1))的概率:
形象的理解一下,就是从 (k) 轮操作中选出 (i) 个要提供伤害的贡献,剩下 (k - i) 的滚到负无穷。
这预处理都可以 (O(N ^ 2)) 时间解决。
预处理完那个东西,我们可以再搞一个 (W_{i, j}) 表示从 (i) 这个点走到 (j) 的概率:
- (W_{i, i+1} = dfrac{1}{m + 1}H_0),即加一贡献到第一个数,减贡献全部到无穷
- (W_{i, j} (n > i ge j) = dfrac{m}{m + 1}H_{i - j} + dfrac{1}{m + 1}H_{i - j + 1}) 就是枚举 (+1) 是否作用在第一个数上,所带来的结果
- (W_{n, j} (n ge j) = H_{n - j}),无论如何都无法 (+1),所以忽略影响即可。
我们并没有考虑到伤害过量的问题,但实际上这些系数都在 (E_0 = 0) 前,所以没有贡献。
真正的转移
考虑一个状态 (i) 只有可能转移到 ([1, i + 1]) 转移过来,枚举即可:
这个玩意具有后效性,比如 (1 Leftrightarrow 2) 相互转化,所以只能高斯消元,建立 (n) 个数 (E_{1-n})的位置数组成的 (n) 元 (1) 次方程,解出未知数即可。
高斯消元
常规的高斯消元是 (O(N^3)) 的,但是此题的矩阵有特殊的性质,可以做到 (O(N^2))。
可以发现此题的矩阵是一个下三角矩阵,即对于一个 (i),与其有关的只有 ([1, i + 1]),剩下系数都是 (0)。
对于此类特殊的矩阵,我们有 (O(N ^ 2)) 的算法。即枚举到一行 (i),的时候,这行的系数不为 (0) 的只有 (i, i + 1) 两个位置,消掉下面的行每行都是 (O(1)) 的(即只用删 (3) 个数)
特判
无解情况:
- (k = 0),永远没法减了
- (m = 0) 且 (n > 1) 且 (k = 1) 每次操作永远保持第一个数不变
时间复杂度
(O(TN^2))
Tips
- 此题貌似有点卡常?LOJ不开 (O_2) 跑步过去,但是落谷轻松跑过也许这就是玄学吧
- 注意多测清零!
- 求组合数的时候可以把分母乘积算出来再算逆元,这样可以少一个 (log)
#include <iostream>
#include <cstdio>
#include <cstring>
#define rint register int
using namespace std;
typedef long long LL;
const int N = 1505, P = 1e9 + 7;
int n, p, m, k, a[N][N], H[N];
int inline power(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (LL) res * a % P;
a = (LL)a * a % P;
b >>= 1;
}
return res;
}
int inline C(int a, int b) {
int res = 1, s = 1;
for (rint i = a, j = 1; j <= b; j++, i--) {
res = (LL)res * i % P;
s = (LL)s * j % P;
}
res = (LL)res * power(s, P - 2) % P;
return res;
}
void inline build() {
int inv = power(m + 1, P - 2);
for (rint i = 0; i <= min(n + 1, k); i++) {
H[i] = (LL)C(k, i) * power(inv, k) % P * power(m, k - i) % P;
}
for (rint i = 1; i <= n; i++) {
a[i][n + 1] = a[i][i] = P - 1;
for (rint j = 1; j <= min(n, i + 1); j++) {
int w;
if (i + 1 == j) w = (LL)H[0] * inv % P;
else if(i == n) w = H[n - j];
else w = ((LL)m * inv % P * H[i - j] % P + (LL)inv * H[i - j + 1] % P) % P;
(a[i][j] += w) %= P;
}
}
}
void inline gauss() {
for (rint i = 1; i <= n; i++) {
int div = power(a[i][i], P - 2);
a[i][i] = 1, a[i][n + 1] = (LL)a[i][n + 1] * div % P;
if (i < n) a[i][i + 1] = (LL)a[i][i + 1] * div % P;
for (rint j = i + 1; j <= n; j++) {
int v = a[j][i]; a[j][i] = 0;
a[j][i + 1] = (a[j][i + 1] - (LL)v * a[i][i + 1] % P + P) % P;
a[j][n + 1] = (a[j][n + 1] - (LL)v * a[i][n + 1] % P + P) % P;
}
}
for (rint i = n - 1; i; i--) {
a[i][n + 1] = (a[i][n + 1] - (LL)a[i][i + 1] * a[i + 1][n + 1] % P + P) % P;
a[i][i + 1] = 0;
}
}
void inline clear() {
for (rint i = 1; i <= n + 1; i++) H[i] = 0;
for (rint i = 1; i <= n; i++)
for (rint j = 1; j <= n + 1; j++) a[i][j] = 0;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d%d%d", &n, &p, &m, &k);
if (k == 0 || (n > 1 && m == 0 && k == 1)) puts("-1");
else {
clear();
build();
gauss();
printf("%d
", a[p][n + 1]);
}
}
return 0;
}