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)\) 解决问题了. 如果前缀和大于后缀和, 我们就将右指针左移, 否则将左指针右移. 如果存在一个时刻前后缀和相等, 那么序列就不合法了.

这道题要我们统计方案数, 可以使用 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;
}

浙公网安备 33010602011771号