1 条题解
-
0
思路
本题所有"奇数位/偶数位"都指原序列的绝对下标(不是子段内部的相对位置),这与样例和提示一致。
为什么只减奇数位。 对任一子段,记 。减小偶数位只会让 变小、约束更难满足;减小奇数位只会让 变大。所以若存在某个最优解,把其中的偶数位操作换成对奇数位的操作不会让答案变差,因此总存在只减奇数位的最优解。
局部约束就足够了。 对每个偶数下标 (,步长 )考虑长度 的子段 ( 时令哨兵 ,等价于子段 ),它要求
若所有这样的三元约束都成立,则任意长度 的子段 都满足平衡条件:按 的奇偶分类,把落在 内的每个偶数位的约束求和,内部奇数位会被相邻偶数位各覆盖一次、端点奇数位至少覆盖一次,子段外的项非负,于是偶数位之和 奇数位之和。
贪心。 从左到右处理每个偶数下标 :
- 令 、。若 ,已满足;否则必须把相邻奇数位合计减少 。
- 只减奇数位,优先减右侧的 (最多减到 ),不够再减左侧 ,共操作 次。
右侧优先不劣于左侧优先: 同时是下一个偶数位 的左邻居,把它减得更小只会让后续约束更容易满足;两种做法对当前约束的减少量相同,所以优先减右侧是安全的。若改为从右往左遍历,按对称性优先减左侧即可。
遍历完所有偶数位后所有局部三元约束成立,数组即为平衡序列;又由必要性,如此累计的操作次数就是最少的。
每个测试用例 ,总复杂度 ,空间复杂度 。
代码
#include<bits/stdc++.h> #define int long long using namespace std; void go(){ int n;cin>>n; vector<int>a(n+2,0); // 多开一位,a[n+1] 作为哨兵 0 for(int i=1;i<=n;i++){ cin>>a[i]; } int sum = 0; // 累计操作次数 for(int i=2;i<=n;i+=2){ // 只检查偶数下标的局部约束 int odd = a[i-1]+a[i+1];// 相邻两个奇数位之和 int even = a[i]; // 当前偶数位 if(odd>even){ // 不满足局部约束 int d = odd-even; // 需要减少的总量 sum += d; // 共需操作 d 次 int t = min(a[i+1], d);// 优先从右侧奇数位减少 a[i+1] -= t; d -= t; // 更新还需减少的量 t = min(a[i-1],d); // 右侧不够再从左侧奇数位减少 a[i-1] -= t; } } cout<<sum<<'\n'; for(int i=1;i<=n;i++){ cout<<a[i]<<' '; } cout<<'\n'; } signed main() { ios::sync_with_stdio(false); cin.tie(nullptr);cout.tie(nullptr); int t=1; cin >>t; // 多组测试数据 while(t--){ go(); } return 0; }
- 1
信息
- ID
- 28
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者