Hungry Games§
可以通过逆向思维将题意转化为求
g最终值为0的方案数。我们发现,当从某个位置出发模拟题目操作时,
g为0时所在的位置十分特殊。以
g为0的位置作为截断后会发现,固定任意起点,对于能到达的所有g为0位置的过程都可以看成00之间的跳跃。由于
0点的特殊性,每一段都能独立开进行计算。假如你计算某个位置的方案数,自然就可以利用离该位置最近的那个0号位。那么我们可以倒着计算某个位置的方案数,从而借用
dp的思想求出各位置答案。考虑设计
dp状态,dp[x]代表着跳到x位置时0出现的次数。很显然,如果某个位置由某个状态转移过来,它一定会先跳到一个0位置,因为它们的终止点统一为0。为什么
dp[n] = 1呢?显然,当跳到n号位置时,此时的g必定为0,所以为1。while (s[j - 1] - s[i] > x) { j--; }这一步是为了寻找位置
j使得跳到该位置恰好为0。利用双指针思想能够线性的找出所需位置,当然,为了确保每一段的正确性,需要找最接近的0。
dp[i] = 1 + (j <= n ? dp[j] : 0)转移状态就很好理解了。但是
ans -= dp[i] - 1为什么-1呢?在这里我们的状态并不是某个位置出发的方案数,而是跳到某个位置的方案数。
因此我们外层循环下标从
0开始,但是我们也可以很方便求下标从某个位置开始的方案数。假如得到跳到下标
x的方案数,要求出下标从x + 1开始的方案数,我们只需要简单的减去1就可以了。因为跳到下标
x的方案数包含了在下标x上的情况,这时g确实为0。但我们实际不需要加上这一次。因为我们会从下标x + 1开始。综上,减去这些过程中的方案数就能得到该题答案。
#include <bits/stdc++.h> using i64 = long long; void solve() { int n, x; std::cin >> n >> x; std::vector<int> a(n); for (int i = 0; i < n; i++) { std::cin >> a[i]; } std::vector<i64> s(n + 1); for (int i = 0; i < n; i++) { s[i + 1] = s[i] + a[i]; } i64 ans = 1LL * n * (n + 1) / 2; std::vector<int> dp(n + 1); dp[n] = 1; for (int i = n - 1, j = n + 1; i >= 0; i--) { while (s[j - 1] - s[i] > x) { j--; } dp[i] = 1 + (j <= n ? dp[j] : 0); ans -= dp[i] - 1; } std::cout << ans << "\n"; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int t; std::cin >> t; while (t--) { solve(); } return 0; }