1 条题解

  • 0
    @ 2026-4-2 22:35:18

    题目题解

    问题理解

    给定数组 aa0ain0 \le a_i \le n),可以执行至多一次操作:选择 i<ji < j,令 ai:=ai+aja_i := a_i + a_jaj:=0a_j := 0
    代价为 jij - i(不操作代价为 00)。
    定义前缀最小值之和:

    S=k=1nmin(a1,a2,,ak).S = \sum_{k=1}^n \min(a_1, a_2, \dots, a_k).

    对于每个 x=0,1,,n1x = 0, 1, \dots, n-1,求在所有代价 x\ge x 的操作中 SS 的最大值。


    第一步:忽略代价,求最大 SS

    先考虑最优操作 (i,j)(i, j) 应满足什么性质。

    观察 1
    如果 ii 不是前缀最小值(即存在 p<ip < i 使得 ap<aia_p < a_i),则增加 aia_i 不会改变前缀最小值序列(因为 apa_p 仍是更小的前缀最小值),因此操作无意义。
    所以 ii 必须是前缀最小值。

    观察 2
    如果 jj 不是后缀最大值(即存在 q>jq > j 使得 aq>aja_q > a_j),则我们可以选择 qq 代替 jj,因为:

    • ai+aqai+aja_i + a_q \ge a_i + a_j,所以 aia_i 增加更多(或相等);
    • aja_j00 的影响:由于 aq>aja_q > a_j,将 aqa_q00 可能使后续前缀最小值更小,但我们可以通过调整 ii 的位置来补偿。实际上可以证明,选择更大的 jj(后缀最大值)不会使答案变差。

    因此,我们只需考虑 ii 是前缀最小值,jj 是后缀最大值。


    第二步:枚举候选 (i,j)(i, j)

    由于 aina_i \le nii 是前缀最小值,这样的 ii 最多 nn 个。
    后缀最大值也最多 nn 个。
    因此候选对 (i,j)(i, j) 数量为 O(n)O(n)(实际上更少,因为 i<ji < jai+aja_i + a_j 不能超过某个上界)。

    具体地,对于每个前缀最小值 ii,我们尝试所有后缀最大值 j>ij > i,直到 ai+aj之前的某个前缀最小值a_i + a_j \ge \text{之前的某个前缀最小值}(因为再增加 aia_i 也不会改变前缀最小值序列)。这样每个 ii 只需尝试 O(1)O(1)jj


    第三步:计算操作后的 SS

    设我们固定 (i,j)(i, j),操作后 ai=ai+aja_i' = a_i + a_jaj=0a_j' = 0

    我们需要快速计算新数组的 SS
    SS 分解为若干部分:

    1. 前缀部分:前 i1i-1 个元素不受影响,其贡献为 pref[i1]\text{pref}[i-1](已预处理)。
    2. 中间部分:从 ii 到某个位置 k1k-1,前缀最小值变为 x=min(ai,MN[i1])x = \min(a_i', \text{MN}[i-1]),其中 MN[i1]\text{MN}[i-1] 是前 i1i-1 个元素的最小值。
      kk 是第一个位置 >i> i 使得 akxa_k \le x(用稀疏表 + 二分查找)。
      iik1k-1 的贡献为 x(ki)x \cdot (k - i)
    3. 剩余部分:从 kknn,前缀最小值由 ak,ak+1,a_k, a_{k+1}, \dots 决定。
      我们可以预处理一个“后缀最小值贡献”数组 suffix(k),表示从 kk 开始的 SS 值(假设 kk 是当前最小)。
      计算 suffix(k) 可用单调栈:找到第一个 l>kl > k 使得 al<aka_l < a_k,则$$\text{suffix}(k) = \text{suffix}(l) + a_k \cdot (l - k). $$
    4. 减去 jj 的影响:由于 aja_j 被置 00,从 jj 开始的前缀最小值可能变小。
      设 $M = \min(\text{前 } i-1 \text{ 的最小值}, x, a_{i+1}, \dots, a_{j-1})$。
      找到第一个 mjm \ge j 使得 amMa_m \le M,则 jjm1m-1 的贡献原为 M(mj)M \cdot (m - j),但现在应改为 00(因为 aj=0a_j = 0 使前缀最小值变为 00),因此需要减去这部分。

    将以上四部分相加,即得操作后的 SS


    第四步:处理代价

    对于每个候选对 (i,j)(i, j),其代价为 d=jid = j - i,我们得到对应的 SS 值。
    我们用 best[d] = max(best[d], S) 更新。
    最后,从大到小取后缀最大值:best[d] = max(best[d], best[d+1]),因为代价 d\ge d 的最大值就是 max(best[d],best[d+1],)\max(best[d], best[d+1], \dots)

    最终输出 best[0]best[n-1]


    第五步:时间复杂度

    • 枚举 (i,j)(i, j)O(n)O(n)
    • 每次计算 SSO(logn)O(\log n)(二分查找)。
    • 总复杂度 O(nlogn)O(n \log n),可处理 n106n \le 10^6

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxN = 1e6 + 10;
    int a[maxN];
    long long best[maxN];
    long long pref[maxN];
    int sec_mn[maxN];
    long long pref_sec[maxN];
    int MN[maxN];
    int f[maxN];
    
    void solve() {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
        }
        
        for (int i = 0; i <= n - 1; i++) {
            best[i] = 0;
        }
        
        // 后缀最大值
        vector<int> suf_max;
        for (int i = n; i >= 1; i--) {
            if (suf_max.empty() || a[i] > a[suf_max.back()]) {
                suf_max.push_back(i);
            }
        }
        
        // 预处理前缀最小值和次小值
        int mn = 1e9;
        sec_mn[0] = 1e9;
        MN[0] = 1e9;
        int ind = -1;
        for (int i = 1; i <= n; i++) {
            sec_mn[i] = sec_mn[i - 1];
            if (a[i] < mn) {
                if (ind != -1) {
                    f[ind] = i;
                }
                sec_mn[i] = mn;
                mn = a[i];
                ind = i;
            } else if (a[i] < sec_mn[i]) {
                sec_mn[i] = a[i];
            }
            MN[i] = mn;
            pref[i] = pref[i - 1] + mn;
            pref_sec[i] = pref_sec[i - 1] + sec_mn[i];
        }
        f[ind] = n + 1;
        
        best[0] = pref[n];
        
        // 单调栈预处理
        vector<int> st{n + 1};
        a[n + 1] = -1e9;
        for (int i = n; i >= 1; i--) {
            if (MN[i - 1] > a[i]) {
                for (int id : suf_max) {
                    if (id <= i) break;
                    int cur_mn = min(a[i] + a[id], MN[i - 1]);
                    // 二分查找第一个 <= cur_mn 的位置
                    int l = 0, r = st.size();
                    while (r - l > 1) {
                        int mid = (l + r) / 2;
                        if (a[st[mid]] <= cur_mn) l = mid;
                        else r = mid;
                    }
                    int upto = min(id - 1, st[l] - 1);
                    long long val = pref[i - 1] + 1LL * cur_mn * (upto - i + 1);
                    if (upto + 1 < id) {
                        int upup = min(f[i], id);
                        val += pref_sec[upup - 1] - pref_sec[upto];
                        val += pref[id - 1] - pref[upup - 1];
                    }
                    best[id - i] = max(best[id - i], val);
                    if (a[i] + a[id] >= MN[i - 1]) break;
                }
            }
            while (!st.empty() && a[i] <= a[st.back()]) st.pop_back();
            st.push_back(i);
        }
        
        // 后缀最大值
        for (int i = n - 2; i >= 0; i--) {
            best[i] = max(best[i], best[i + 1]);
        }
        
        for (int i = 0; i < n; i++) {
            cout << best[i] << " \n"[i == n - 1];
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int t;
        cin >> t;
        while (t--) {
            solve();
        }
        
        return 0;
    }
    

    总结

    本题的关键在于:

    1. 限制 ii 为前缀最小值,jj 为后缀最大值,从而将候选对数量降至 O(n)O(n)
    2. SS 分解为若干部分,利用预处理的前缀和、次小值、单调栈等快速计算。
    3. 通过后缀最大值得到每个代价 x\ge x 的最优答案。
    • 1

    信息

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