1 条题解
-
0
思路
记总能量 。一次操作把数组从某个切割点分成左右两段,要求两段的抽取量之和相等。
无解判定
-
每次操作中切割点两侧的抽取量相等,所以本次抽取总量是偶数,于是 必须是偶数;
-
设某次操作两侧的和都是 ,则该次每个节点抽走的能量都不超过 。若共进行 次操作,则
$$a_i=\sum_{j=1}^{k}b_i^{(j)}\le\sum_{j=1}^{k}X_j=S/2$$因此必须有 。
所以 为奇数或 时直接输出 。
构造(最多 2 次操作)
-
若存在 使前缀和 ,直接取 ,以 为切割点,1 次即可全部清零。
-
否则取最小的 使 ,令 ,则 。本次抽取构造为:
- ,;
- 从 向前依次抽取 ,直到右侧共抽走 。
这样 以 为切割点:左侧()恰为 ,右侧()也恰为 ,操作合法。
剩余数组 的总和为 ,且前 项之和恰为 ,于是第二次操作直接取 、以 为切割点即可清零。
由 可保证 且 ,两个切割点都合法,右侧也一定放得下 的抽取量(因为 )。
代码先输出剩余数组、再输出本次抽取量,两行都是合法的 序列,执行顺序可以互换。
复杂度: 时间 ,空间 。
代码
#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
- 上传者