洛谷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\) 的方案数。则有转移:

\[\mathrm{dp}(i, j) = \sum_{h_i = j}^{H_i} \mathrm{dp}(i - 1, h_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;
}
posted @ 2022-03-16 22:33  duyiblue  阅读(225)  评论(0编辑  收藏  举报