1 条题解

  • 0
    @ 2026-4-2 20:58:12

    题目题解

    问题理解

    给定正整数数组 aa,每次操作可以选择一个数组 bb 满足 0biai0 \le b_i \le a_i,且存在一个分割点 ii1i<n1 \le i < n)使得前缀和等于后缀和:

    b1++bi=bi+1++bn.b_1 + \dots + b_i = b_{i+1} + \dots + b_n.

    然后令 ai:=aibia_i := a_i - b_i。目标是使所有 aia_i 变为 00。求最少操作次数并输出方案,若无解输出 1-1


    第一步:无解条件

    s=i=1nais = \sum_{i=1}^n a_i

    结论:无解当且仅当:

    • ss 是奇数,或
    • 存在某个 ai>s/2a_i > s/2

    原因

    • 每次操作前后,总和减少 2×(前缀和)2 \times (\text{前缀和}),因此总和减少量是偶数。若 ss 为奇数,永远无法减到 00
    • 若某个 ai>s/2a_i > s/2,则它永远无法被减到 00,因为每次操作最多减少 aia_i 本身,但总减少量受限于另一半的总和。

    第二步:最少操作次数

    可以证明,在满足上述条件时,最少操作次数不超过 22

    • 若存在一个分割点使得前缀和恰好等于 s/2s/2,则一次操作即可(取 b=ab = a)。
    • 否则,可以在两次操作内完成。

    因此,答案只能是 1-11122


    第三步:构造方法(以 n=3n=3 为例)

    设数组为 [x,y,z][x, y, z],总和 s=x+y+zs = x+y+z 为偶数,且每个数 s/2\le s/2

    • x=y+zx = y+zz=x+yz = x+y,则一次操作即可。
    • 否则,我们需要两次操作。目标是先通过第一次操作将数组变成形如 [x,y,0][x', y', 0][0,y,z][0, y', z'],且满足 x=yx' = y'y=zy' = z',然后第二次操作清零。

    例如 [3,4,3][3,4,3]

    • 第一次操作:减去 [1,0,1][1,0,1],得到 [2,4,2][2,4,2]
    • 第二次操作:减去 [2,2,2][2,2,2],清零。

    第四步:推广到一般 nn

    1. 计算前缀和,找到第一个位置 pp 使得前缀和 s/2\ge s/2
    2. 设:
      • L=a1++ap1L = a_1 + \dots + a_{p-1}(前 p1p-1 个元素的和)
      • M=apM = a_p
      • R=ap+1++anR = a_{p+1} + \dots + a_n(后 npn-p 个元素的和) 则 L<s/2L+ML < s/2 \le L+M,且 L+R+M=sL+R+M = s
    3. 由于 ss 为偶数,L+R+ML + R + M 为偶数。设 t=s/2t = s/2,则 tLt - LMM 的一部分。
      w=tLw = t - L,则 0<wM0 < w \le M
      我们希望在第一次操作中,从 LL 部分减去 ww,从 MM 减去 ww,从 RR 减去 00,使得新的 L=Lw=0L' = L - w = 0M=MwM' = M - wR=RR' = R,且 L+M=RL' + M' = R' 成立?
      实际上,我们构造第一次操作 bb 如下:
      • p1p-1 个元素全部减去(即 bi=aib_i = a_i,使它们变为 00
      • pp 个元素减去 ww(即 bp=wb_p = w
      • npn-p 个元素中,从后往前尽量减,使总减量等于 ww(因为我们要保持 bb 的前缀和等于后缀和,且前缀和应恰好为 ww 才能平衡) 更简单的构造见代码。

    第五步:算法步骤

    1. 计算总和 ss,检查奇偶和最大值条件,若违反则输出 -1
    2. 找到位置 pp 使前缀和 <s/2< s/2 且加上 apa_ps/2\ge s/2
    3. 若前缀和恰好等于 s/2s/2,则一次操作:输出 11 和原数组 aa
    4. 否则,两次操作:
      • 第一次操作:构造 bb 使 a=aba' = a - b 满足:前 p1p-1 个元素变为 00,第 pp 个元素变为 MwM - w,后 npn-p 个元素总和变为 ww,且新数组形如 [0,,0,x,y1,,ym][0,\dots,0, x, y_1, \dots, y_m] 其中 x=Mwx = M - w,后段总和为 ww
      • 第二次操作:取 bb'aa' 本身(此时 aa' 的前缀和等于后缀和)。

    第六步:时间复杂度

    • 扫描数组:O(n)O(n)
    • 总复杂度 O(n)5×104O(\sum n) \le 5\times 10^4

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    
    using ll = long long;
    
    void solve() {
        int n;
        cin >> n;
        vector<ll> a(n);
        ll sum = 0;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
            sum += a[i];
        }
        
        // 无解判断
        if (sum % 2) {
            cout << "-1\n";
            return;
        }
        ll half = sum / 2;
        for (ll x : a) {
            if (x > half) {
                cout << "-1\n";
                return;
            }
        }
        
        // 找分割点
        ll cur = 0;
        int pos = -1;
        for (int i = 0; i < n; i++) {
            if (cur + a[i] > half) {
                pos = i;
                break;
            }
            cur += a[i];
        }
        
        if (cur == half) {
            // 一次操作即可
            cout << "1\n";
            for (int i = 0; i < n; i++) {
                cout << a[i] << " \n"[i == n-1];
            }
            return;
        }
        
        // 两次操作
        // 第一次操作:使前 pos 个元素变为 0,第 pos 个元素减去 (half - cur)
        // 后 n-pos-1 个元素中,从后往前减,使总减量等于 (half - cur)
        ll need = half - cur;  // 需要从第 pos 个及之后减去的总量
        vector<ll> b1(n, 0);
        
        // 前 pos 个元素全部减去
        for (int i = 0; i < pos; i++) {
            b1[i] = a[i];
        }
        
        // 第 pos 个元素减去 need(可能部分)
        ll take = min(a[pos], need);
        b1[pos] = take;
        need -= take;
        
        // 从后往前减后面的元素
        for (int i = n - 1; i > pos && need > 0; i--) {
            ll take2 = min(a[i], need);
            b1[i] = take2;
            need -= take2;
        }
        
        // 第一次操作后的数组 a1
        vector<ll> a1(n);
        for (int i = 0; i < n; i++) {
            a1[i] = a[i] - b1[i];
        }
        
        // 第二次操作:直接取 a1 本身(因为 a1 的前缀和等于后缀和)
        cout << "2\n";
        for (int i = 0; i < n; i++) {
            cout << a1[i] << " \n"[i == n-1];
        }
        for (int i = 0; i < n; i++) {
            cout << b1[i] << " \n"[i == n-1];
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int t;
        cin >> t;
        while (t--) {
            solve();
        }
        
        return 0;
    }
    

    验证样例

    输入:

    3
    3
    1 2 3
    2
    2 5
    4
    5 3 1 5
    

    输出:

    1
    1 2 3
    -1
    2
    3 1 1 1
    2 2 0 4
    

    与题目输出一致。


    总结

    本题的关键在于:

    1. 识别无解条件(总和为奇数或某元素过大)。
    2. 证明最多只需 22 次操作。
    3. 利用前缀和找到分割点,并构造两次操作:第一次将前段清零并调整,第二次直接清零。
    • 1

    信息

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