2025.03.08 CW 模拟赛 B. 沉默乐团

B. 沉默乐团

从暴力到动态规划.

题意

我们称一个正整数序列 \( a \) 是好的,当且仅当:

  • 对于每个整数 \( i \) (\( 1 \leq i \leq n \)),都有 \( l_i \leq a_i \leq r_i \);
  • 不存在两个整数 \( i, j \) (\( 1 \leq i < n, 1 < j \leq n \))满足 \(\left( \sum_{k=1}^i a_i \right) = \left( \sum_{k=j}^n a_j \right)\)。

请你统计好的整数序列 \( a \) 的数量对 \( 10^9 + 7 \) 取模的值。

思路

考虑给定一个序列, 我们如何判断这个序列是否合法.

有一个显然的性质: 若一段前缀和一段后缀相等且有交, 那么一定存在一段前缀和后缀相等且无交. \((\)因为交的部分是重复的\()\).

所以我们就可以用双指针 \(\mathcal{O}(n)\) 解决问题了. 如果前缀和大于后缀和, 我们就将右指针左移, 否则将左指针右移. 如果存在一个时刻前后缀和相等, 那么序列就不合法了.

image.png

这道题要我们统计方案数, 可以使用 DP 解决.

根据上面的过程, 我们设 \(f_{i, j, k}\) 表示当前左指针在 \(i\), 右指针在 \(j\), 且当前前缀和减后缀和的差为 \(k\) 的方案数, 最后答案即为 \(f_{1, n, 0}\).

考虑如何转移. 还是像双指针的过程一样, 如果 \(k > 0\), 代表前缀和大于后缀和, 我们需要将右指针左移, 也就是 \(f_{i, j, k} \gets f_{i, j - 1, k - x}\), 其中 \(x \in [l_j, r_j]\). 当 \(k \le 0\) 也是同理.

直接转移是 \(\mathcal{O}(n^2V^2)\) 的, 使用前缀和优化到 \(\mathcal{O}(n^2V)\).

在实现上, 可以使用 map 或者加上一个偏移量防止下标出现负数.

#include "iostream"
#include "cmath"

using namespace std;

constexpr int N = 51, M = 4e3 + 1;
constexpr int mod = 1e9 + 7, B = 2000;

int modx(int x) { return x >= mod ? x - mod : x; }
void addon(int &x, int y) { if ((x += y) >= mod) x -= mod; }

int n, l[N], r[N];
int f[N][N][M], s[N][N][M];

void init() {
	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> l[i] >> r[i];
	for (int i = 1; i <= n; ++i) {
		for (int k = 0; k <= 4000; ++k) if (k ^ B or n == 1)
			f[i][i][k] = s[i][i][k] = r[i] - l[i] + 1 - (abs(k - B) <= r[i] and abs(k - B) >= l[i]);
		for (int k = 1; k <= 4000; ++k) addon(s[i][i][k], s[i][i][k - 1]);
	}
}

void calculate() {
	for (int _ = 2; _ <= n; ++_)
		for (int i = 1, j = _; j <= n; ++i, ++j) {
			for (int k = 0; k <= B; ++k)
				if (k ^ B or (i == 1 and j == n)) f[i][j][k] = modx(s[i + 1][j][k + r[i]] - s[i + 1][j][k + l[i] - 1] + mod);
			for (int k = B + 1; k <= 4000; ++k) f[i][j][k] = modx(s[i][j - 1][k - l[j]] - s[i][j - 1][k - r[j] - 1] + mod);
			s[i][j][0] = f[i][j][0];
			for (int k = 1; k <= 4000; ++k) s[i][j][k] = modx(s[i][j][k - 1] + f[i][j][k]);
		}
	cout << f[1][n][B] << '\n';
}

void solve() {
	cin.tie(nullptr)->sync_with_stdio(false);
	init();
	calculate();
}

int main() {
	solve();
	return 0;
}
posted @ 2025-03-09 14:12  Steven1013  阅读(39)  评论(0)    收藏  举报