1 条题解

  • 0
    @ 2026-10-31 17:58:20

    思路

    记总能量 S=∑aiS=\sum a_i。一次操作把数组从某个切割点分成左右两段,要求两段的抽取量之和相等。

    无解判定

    • 每次操作中切割点两侧的抽取量相等,所以本次抽取总量是偶数,于是 SS 必须是偶数;

    • 设某次操作两侧的和都是 XX,则该次每个节点抽走的能量都不超过 XX。若共进行 kk 次操作,则

      $$a_i=\sum_{j=1}^{k}b_i^{(j)}\le\sum_{j=1}^{k}X_j=S/2$$

      因此必须有 max⁡ai≤S/2\max a_i\le S/2。

    所以 SS 为奇数或 max⁡ai>S/2\max a_i>S/2 时直接输出 −1-1。

    构造(最多 2 次操作)

    1. 若存在 kk 使前缀和 fs[k]=S/2fs[k]=S/2,直接取 b=ab=a,以 kk 为切割点,1 次即可全部清零。

    2. 否则取最小的 pp 使 fs[p]>S/2fs[p]>S/2,令 d=S/2−fs[p−1]d=S/2-fs[p-1],则 1≤d<ap1\le d<a_p。本次抽取构造为:

      • bi=0 (i<p)b_i=0\ (i<p),bp=db_p=d;
      • 从 i=ni=n 向前依次抽取 min⁡(ai,剩余)\min(a_i,\text{剩余}),直到右侧共抽走 dd。

      这样 bb 以 pp 为切割点:左侧(1∼p1\sim p)恰为 dd,右侧(p+1∼np+1\sim n)也恰为 dd,操作合法。

      剩余数组 a′=a−ba'=a-b 的总和为 S−2d=2fs[p−1]S-2d=2fs[p-1],且前 p−1p-1 项之和恰为 fs[p−1]=S′/2fs[p-1]=S'/2,于是第二次操作直接取 b′=a′b'=a'、以 p−1p-1 为切割点即可清零。

    由 max⁡ai≤S/2\max a_i\le S/2 可保证 p≥2p\ge2 且 p<np<n,两个切割点都合法,右侧也一定放得下 dd 的抽取量(因为 ∑i>pai≥S/2−fs[p−1]=d\sum_{i>p}a_i\ge S/2-fs[p-1]=d)。

    代码先输出剩余数组、再输出本次抽取量,两行都是合法的 bb 序列,执行顺序可以互换。

    复杂度: 时间 O(∑n)O(\sum n),空间 O(n)O(n)。

    代码

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    
    const int N=5e4+5;
    int n;
    int a[N],b[N],fs[N];                 // a 当前剩余能量、b 本次抽取量、fs 前缀和
    
    void go(){
        cin>>n;
    
        int mx = 0, sum = 0;
        for(int i=1;i<=n;i++){
            cin>>a[i];
    
            b[i]=0;                      // 初始化抽取数组
            mx = max(mx,a[i]);
            sum += a[i];
        }
        // 总和为奇数或最大值超过总和一半时,无论怎么操作都无法清零
        if(sum%2==1 or mx>sum/2){
            cout<<"-1\n";
            return;
        }
        // 计算前缀和
        for(int i=1;i<=n;i++){
            fs[i]=fs[i-1]+a[i];
        }
    
        int pos = 0;
        for(int i=1;i<=n;i++){
            // 前缀和恰为总和一半:一次操作即可全部抽走
            if(fs[i]==sum/2){
                cout<<"1"<<endl;
                for(int j=1;j<=n;j++) cout<<a[j]<<' ';
                cout<<endl;
                return;
    
            }else if(fs[i]>sum/2){
                // 找到第一个前缀和超过一半的位置,一次操作做不到,需要两次
                pos = i;
                break;
            }
        }
    
        // 让左侧(1~pos)与右侧(pos+1~n)各抽走 d,使本次抽取合法
        int extra = sum/2 - fs[pos-1];   // 还差多少才能让左侧和达到总和一半
        a[pos] -= extra;                 // 从 pos 处抽走 extra
        b[pos] = extra;                  // 记录本次抽取
    
        // 从右往左贪心抽取,凑够右侧的 extra
        for(int i=n;i>pos;i--){
            int val = min(a[i],extra);   // 该位置最多还能抽多少
            a[i] -= val;                 // 更新剩余能量
            b[i] += val;                 // 记录本次抽取
            extra -= val;                // 更新剩余需要抽走的量
            if(!extra) break;            // 已经抽够,提前结束
        }
    
        // 输出两次操作:剩余数组再整体抽一次即可清零
        cout<<"2"<<endl;
        for(int i=1;i<=n;i++){cout<<a[i]<<' ';}   // 第二次操作的抽取量
        cout<<endl;
        for(int i=1;i<=n;i++){cout<<b[i]<<' ';}   // 第一次操作的抽取量
        cout<<endl;
    }
    
    signed main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);cout.tie(nullptr);
        int _=1;
        cin >>_;                         // 多组测试数据
        while(_--){
            go();
        }
        return 0;
    }
    
    • 1

    信息

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