1 条题解

  • 0
    @ 2026-5-5 15:27:38

    题意简述

    给定长度为 nn 的数组 aa 和阈值 xx。对于每个子段 [l,r][l, r],从左到右累加 aia_i 到计数器 gg,若中途 g>xg > x 则将 gg 重置为 00。求最终 g0g \neq 0 的子段个数。

    关键观察

    当遇到某个位置 ii 使得前缀和超过 xx 时,gg 就会清零。此后,该位置右侧的统计相当于从该位置重新开始计数。

    我们可以从右往左进行 DP。设 dp[i]dp[i] 表示从第 ii 个蘑菇开始,以它为左端点 ll,且最终 g0g \neq 0 的右端点 rr 的个数(也就是对所有 rir \ge i,子段 [i,r][i, r] 最后 g0g \neq 0 的数量)。注意,这里的计数方式依赖于代码中的定义:代码中 dp[i]dp[i] 实际上统计的是“以 ii 开头且最终 g0g \neq 0 的子段数”,并且利用了前缀和和二分。

    推导

    令前缀和数组 pp,其中 pi=j=1iajp_i = \sum_{j=1}^{i} a_jp0=0p_0=0)。当我们从位置 ii 开始累加时,如果在位置 q1q-1 处(即 pq1pi1xp_{q-1} - p_{i-1} \le x)仍满足条件,而在 qq 处首次超过 xx,则游戏会在 qq 处清零,后续相当于从 q+1q+1 重新开始。

    那么对于左端点 ii

    • 如果一直累加到末尾都不超过 xx,则所有右端点 r[i,n]r \in [i, n] 都满足最终 g0g \neq 0,贡献为 ni+1n - i + 1
    • 否则,设第一个超过 xx 的位置为 qq,那么在 [i,q1][i, q-1] 范围内的右端点都满足条件,因为在这些右端点处游戏还未清零;而 qq 处会清零,之后的情况等价于以 q+1q+1 为起点重新开始。因此有递推关系:dp[i]=(qi)+dp[q+1]dp[i] = (q - i) + dp[q+1] 其中 qq 是满足 pqpi1>xp_q - p_{i-1} > x 的最小位置。

    在代码实现中,将 dpdp 数组倒过来计算,并利用前缀和 aa(实际代码中 aa 被原地改为前缀和)和二分查找 qqq = upper_bound(a.begin(), a.end(), a[i] + x) - a.begin();(注意这里 a[i]a[i] 是前缀和 pip_i,所以 a[i]+xa[i] + x 对应 pi+xp_i + x,二分查找第一个 >pi+x> p_i + x 的位置 qq)。于是 dp[i]=dp[q]+qi1dp[i] = dp[q] + q - i - 1,含义为:从 ii 开始,清零发生在 qq(注意下标换算),[i,q1][i, q-1] 内的右端点有 (qi1)(q - i - 1) 个,再加上从 qq 开始的后续方案 dp[q]dp[q]

    最终答案就是 i=0n1dp[i]\sum_{i=0}^{n-1} dp[i](代码中 ii00n1n-1)。

    时间复杂度

    对每个测试用例,前缀和 O(n)O(n),反向遍历 O(n)O(n),每次二分 O(logn)O(\log n),总复杂度 O(nlogn)O(n \log n),满足 n2×105\sum n \le 2 \times 10^5 的限制。

    参考代码

    #include <bits/stdc++.h>
    using namespace std;
    
    using ll = long long;
    
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
    
        int tests;
        cin >> tests;
        while (tests--) {
            int n;
            ll x;
            cin >> n >> x;
            vector<ll> a(n + 1);
            for (int i = 1; i <= n; ++i) cin >> a[i];
            // 计算前缀和,原地覆盖
            partial_sum(a.begin() + 1, a.end(), a.begin() + 1);
            vector<int> dp(n + 2, 0); // dp[n+1] 默认为0
            for (int i = n - 1; i >= 0; --i) {
                int q = upper_bound(a.begin(), a.end(), a[i] + x) - a.begin();
                dp[i] = dp[q] + q - i - 1;
            }
            cout << accumulate(dp.begin(), dp.begin() + n, 0ll) << '\n';
        }
    }
    
    • 1

    信息

    ID
    6917
    时间
    2000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者