P10037 「FAOI-R2」梨花开 (C) 题解
比较有意思的 dp 题。
思路
考虑答案至少为 \(p\) 的条件是什么。
- 我们显然会把根节点的热量平均分配到深度前 \(p\) 小的点上,那么要求 \(\min_{j=1}^n \sum_{i=1}^j v_i+ \frac{k}{p}\ge 0\)。
- 其次,在热量到达前 \(k\) 小的点前,\(\sum_{i=1} v_i\) 不可以小于 \(0\)。
只要满足了这两个条件,我们的答案就至少为 \(p\),假设这样的方案数为 \(f_p\)。
那么答案为:
如何求解。
设 \(dp_{i,j}\) 为第 \(i\) 步后 \(\sum_{k=1}^{i}v_k=j\) 的方案数。
在转移时可以判断一下是否合法即可。
转移可以用前缀和优化。
复杂度:\(O(nmt^2)\)。
可以获得 \(20\) 分。
发现我们的限制只和 \(dep_i\) 与 \(\frac{k}{p}\) 有关。
因为操作只有 \(t\) 次,所以要求 \(dep_i\le t\)。
而根据整除分块,\(\frac{k}{p}\) 只有根号项,所以记忆化一下,可以优化复杂度。
复杂度:\(O((t+\sqrt n)mt^2)\)。
视常数可以获得 \(50\sim 60\) 分。
容易发现我们满足前缀和不小于 \(0\) 的 dp 都是一样的操作。
那么我们可以进行预处理。
设 \(f_{i,j}\) 表示在 \(i\) 轮后满足前缀和一直不小于 \(0\),且 \(\sum_{k=1}^i v_k=j\) 的方案数。
转移同样枚举下一位然后前缀和优化。
同样,我们想要仿造的求出 \(g_{i,j}\) 表示 \(i\) 轮后满足前缀和一直不小于 \(j\) 的方案数。
这是我们直接 dp 的后半段。
这个怎么求呢?
这类问题好像有一个比较常见的 trick。
就是在维护前缀和的最小值时,我们正着不好做,但是反过来是很好求的。
考虑反过来怎么求前缀和的最小值:
这个式子我们就可以 dp 了。
求出了 \(g_{i,j}\) 以后,就可以轻松搭配 \(f_{i,j}\) 求出这个询问的答案了。
时间复杂度:\(O((\sqrt k+t)mt+mt^2)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define eb(...) emplace_back(__VA_ARGS__)
#define fro(i, x, y) for (int i = (x); i <= (y); i++)
#define pre(i, x, y) for (int i = (x); i >= (y); i--)
const int N = 1e6 + 10;
const int mod = 998244353;
int n, m, t, k, ans, d[N], R[N], sm[N];
int dp1[510][30000];
int dp2[510][30000];
vector<int> to[N];
inline void dfs(int x, int f) {
d[x] = d[f] + 1;
for (auto i : to[x])
if (i != f) dfs(i, x);
}
inline void add(int&x, int y) {
x = (x + y >= mod ? x + y - mod : x + y);
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m >> t >> k;
fro(i, 1, n - 1) {
int u, v;
cin >> u >> v;
to[u].eb(v);
to[v].eb(u);
}
dfs(1, 0);
sort(d + 1, d + n + 1);
dp1[0][R[0] = 0] = dp2[0][R[0] = 0] = 1;
fro(i, 1, t) {
fro(j, 0, R[i - 1]) {
add(dp1[i][max(0, j - m)], dp1[i - 1][j]);
add(dp1[i][j + m + 1], mod - dp1[i - 1][j]);
add(dp2[i][max(0, j - m)], dp2[i - 1][j]);
add(dp2[i][j + m + 1], mod - dp2[i - 1][j]);
}
R[i] = R[i - 1] + m;
fro(j, 1, R[i] + 1) add(dp1[i][j], dp1[i][j - 1]);
fro(j, 1, R[i] + 1) add(dp2[i][j], dp2[i][j - 1]);
fro(j, 0, R[i - 1]) if (m - j > 0) add(dp2[i][0], 1ll * (m - j) * dp2[i - 1][j] % mod);
}
fro(i, 1, t) fro(j, 1, R[i]) add(dp2[i][j], dp2[i][j - 1]);
fro(i, 1, n) {
int v = k / i, p = min(t + 2, d[i]);
if (i == 1) {
sm[i] = dp2[t][min(v, R[t])];
} else if (v != k / (i - 1) || p != min(t + 2, d[i - 1])) {
int l = p - 2;
int r = t - p + 2;
fro(j, 0, R[l]) add(sm[i], 1ll * dp1[l][j] * dp2[r][min(v + j, R[r])] % mod);
} else sm[i] = sm[i - 1];
add(ans, sm[i]);
}
cout << ans << "\n";
return 0;
}