CF2079B Arithmetic Exercise 题解
在阅读题解的过程中,你可能需要用到该云剪贴板中的部分代码辅助理解。
观察到每个元素
据此可以设计一个
上面的暴力 DP 实现可以参考云剪贴板中的 Code 1。
观察到这种“平移”的形式很像 slope trick 能做的东西,但是现在看起来还有一点麻烦。事实上,我们只需要对它做两个小修改即可:
- 令答案初始值为
-\sum\limits_{i=1}^m x_i ,在第i 轮 DP 时,对于上面第二个转移方程,将该轮的 DP 数组平移一位使其与上一轮的相同位置对齐,这样第一个转移方程变为f_{i,j}\gets f_{i-1,j-2}+2x_i ,第二个转移方程就可以忽略掉;(见云剪贴板中的 Code 2) - 观察到事实上每一轮的
j-2\to j 可以改为j-1\to j ,因为在第奇数轮偶数的位置显然不会有值,反之亦然。当然这个时候数组下标的范围也需要进行一些修改。(见云剪贴板中的 Code 3)
于是该问题被转化成了一个经典的 slope trick 问题,可以使用 multiset 维护。
放代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while(t--){
int n,m; cin>>n>>m;
vector<int> a(m);
for(auto &i:a)cin>>i;
reverse(a.begin(),a.end());
long long w=-accumulate(a.begin(),a.end(),0ll);
multiset<int,greater<> > s;
for(int i=0,p=0;i<m;i++){
int l=i+2>>1,r=i+n+1>>1;
// 当前能够参与转移的下标的范围
s.emplace(a[i]<<1);
while(p<l)w+=*s.begin(),s.erase(s.begin()),p++;
while(p+s.size()>r)s.erase(prev(s.end()));
} // DP 过程
for(int i:s)if(i>0)w+=i;
cout<<w<<'\n';
}
return 0;
}