洛谷P8096 「USACO 2022.1 Gold」Drought
洛谷P8096 「USACO 2022.1 Gold」Drought
题目大意
对于某个整数序列 \(h_1, h_2, \dots, h_n\),定义一种操作:选取一个位置 \(i\),满足 \(1\leq i < n\) 且 \(h_i, h_{i + 1}\geq 1\),令 \(h_i\) 和 \(h_{i + 1}\) 同时减 \(1\)。
称一个序列是合法的,当且仅当能通过若干次操作,使它所有位置上的值相等。
现在给出 \(n\) 和每个位置上 \(h_{i}\) 的上界 \(H_{i}\)(即 \(0\leq h_i\leq H_{i}\)),问有多少个合法的 \(h\) 序列。
数据范围:\(1\leq n\leq 100\),\(0\leq H_i \leq 1000\)。
本题题解
要想计数,我们需要先搞清楚一个序列合法的充分必要条件。
因为只能对相邻位置操作,一个经典的套路是把所有位置按下标的奇偶性分类。这样的好处是,每次操作恰好会碰到两类位置各 \(1\) 个。
设序列的前 \(i\) 个位置里,奇数位置上数的和为 \(o_i\),偶数位置上数的和为 \(e_i\)。考虑某个 \(i\),不妨假设 \(i\) 是奇数,那么 \(h\) 序列合法的一个必要条件是:\(o_i \geq e_i\)。理由如下:最终的序列,显然满足 \(o_i\geq e_i\)(所有位置上数都相等,但奇数位置比偶数位置多一个)。任意一次操作 \((p, p + 1)\),若 \(p < i\),那么不会改变 \(o_i - e_i\) 的值;若 \(p = i\),反而会使 \(o_i - e_i\) 变得更小(\(o_i\) 减 \(1\),\(e_i\) 不变)。所以只有初始时 \(o_i\geq e_i\),最终才能 \(o_i\geq e_i\)。同理,若 \(i\) 是偶数,则需要满足 \(e_i\geq o_i\)。
为了方便,我们定义一个 \(d_i\):若 \(i\) 是奇数,\(d_i = o_i - e_i\);若 \(i\) 是偶数,\(d_i = e_i - o_i\)。不难发现:\(d_i = h_i - d_{i - 1}\)。根据刚刚的分析,序列合法的一个必要条件是 \(\forall i: d_i\geq 0\)。任意一次操作 \((p, p + 1)\) 对 \(d\) 序列的影响是:让 \(d_p\) 减 \(1\),其他值均不变。
考虑最终的 \(d\) 序列是什么样。不妨设最终的 \(h\) 序列上所有值均为 \(k\)。那么 \(d_1 = d_3 = d_5 = \dots = k\),\(d_2 = d_4 = d_6 = \dots = 0\)。我们的目标就是要把 \(d\) 序列变成这个样子。我们的工具是,可以通过一次操作,使任何一个 \(p < n\) 的位置的 \(d_p\) 减 \(1\)。除此之外,我们没有任何限制!换言之,原来那个对 \(h\) 序列进行操作的问题,彻底被我们转化为了对 \(d\) 序列进行操作的问题。而且 \(d\) 序列的操作比 \(h\) 简洁得多。另外,\(d\) 和 \(h\) 序列显然是一一对应的(知道 \(h\) 可以构造出 \(d\),知道 \(d\) 可以构造出 \(h\)),所以接下来我们只要求合法的 \(d\) 的数量即可。
不同于 \(h\),\(d\) 合法的充分必要条件是很显然的。具体来说,若 \(n\) 是偶数,我们需要 \(\forall i < n: d_i \geq 0\),且 \(d_n = 0\) 即可;若 \(n\) 是奇数,我们需要 \(\forall i < n \text{ and }i\text{ is odd}: d_i \geq k\),\(\forall i < n \text{ and } i \text{ is even}: d_i \geq 0\),且 \(d_n = k\) 即可(这里假设 \(k\) 是我们已枚举的一个常数)。为了求这样的序列数量,可以做 DP。设 \(\mathrm{dp}(i, j)\) 表示考虑了前 \(i\) 个位置,\(d_i = j\) 的方案数。则有转移:
若 \(n\) 是偶数,最终答案就是 \(\mathrm{dp}(n, 0)\)。若 \(n\) 是奇数,定义状态时要加上一个条件:当 \(i\) 是奇数时 \(j\geq k\)。最终答案是所有 \(k\) 对应的 \(\mathrm{dp}(n, k)\) 之和。
直接 DP 的时间复杂度是 \(\mathcal{O}(nH^2)\)。用前缀和可以优化到 \(\mathcal{O}(nH)\)。当 \(n\) 是奇数时还需要在外层枚举 \(k\),所以此时的总时间复杂度是 \(\mathcal{O}(nH^2)\)。
参考代码
// problem: P8096
#include <bits/stdc++.h>
using namespace std;
#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 100, MAXH = 1000;
const int MOD = 1e9 + 7;
int n, H[MAXN + 5], dp[MAXN + 5][MAXH + 5], sum[MAXH + 5];
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> H[i];
}
if (n % 2 == 0) {
// n 是偶数
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= H[i]; ++j) { // d[i]
for (int h = j; h <= H[i]; ++h) { // d[i] = h - d[i - 1]
dp[i][j] = (dp[i][j] + dp[i - 1][h - j]) % MOD;
}
}
}
cout << dp[n][0] << endl;
} else {
int ans = 0, lim = MAXH;
for (int i = 1; i <= n; i += 2) {
// 对所有奇数位置:k <= d[i] <= h[i] <= H[i]
// 所以 k 的上限是这些 H[i] 的 min
ckmin(lim, H[i]);
}
for (int k = 0; k <= lim; ++k) {
dp[0][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= MAXH; ++j)
dp[i][j] = 0; // 清空
for (int i = 1; i <= n; ++i) {
sum[0] = dp[i - 1][0];
for (int j = 1; j <= MAXH; ++j) {
sum[j] = (sum[j - 1] + dp[i - 1][j]) % MOD;
}
for (int j = (i % 2 == 0 ? 0 : k); j <= H[i]; ++j) { // d[i]
/*
for (int h = j; h <= H[i]; ++h) { // d[i] = h - d[i - 1]
dp[i][j] = (dp[i][j] + dp[i - 1][h - j]) % MOD;
}
*/
dp[i][j] = sum[H[i] - j];
}
}
ans = (ans + dp[n][k]) % MOD;
}
cout << ans << endl;
}
return 0;
}