CF1852C Ina of the Mountain
*2400 https://codeforces.com/problemset/problem/1852/C
如果没有 \(\mod k\) 的限制的话,我们都会做,因为都是正数,那么 \(\sum_i^n d_i>0\),因此,答案即为 \(\sum[d_i>0]d_i\)。
但是现在多了一个操作,即为区间加 \(k\),那么转到差分数组就是 \(d_l+k,d_r-k\),且该操作不花费。
观察,差分数组的值域范围为 \([1-k,k-1]\)。
那么,你现在要多用这个操作,使得 \(\sum[d_i>0]d_i\) 取 \(\min\)。
考虑扫过去,若 \(d_i\le 0\),那么显然是不在和式里的,但是,它可以执行新操作,为后面提供 \(-k\) 的机会。但是新操作会用多少次呢?显然至多一次,因为如果 \(d_l>0\),那么我们对 \(d_l+k\) 是没意义的。因为这样多了 \(k\) 的贡献,但是后面至多减少 \(k\) 的贡献,那么这样肯定是不优的。我常常将这种区间操作然后提供机会的东西抽象成流量,也就是说,当前会提供 1 的流量。
若 \(d_i>0\),那么它显然只能消耗流量。如果没有流量给他消耗了,那么直接贡献到和式当中。如果有流量,仿照费用流的贪心,如果流过之后,和式更大,那么显然不流更优。同样的,仿照网络流问题,对于一个流量的消耗,我们总需要建立反向边,让他有机会退流,得到反悔的效果。那么,在这里,这个反向边实际上的作用就是再提供一个流量。
那么,对贡献写一下柿子即可,若 \(d_i>0\),选了一个 \(d_j<0\) 流量,那么贡献要么流 \(d_j+m\),要么不流,\(d_i\),注意类添加反向边即可。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=(int)(2e5+5);
multiset<int>s;
int n,a[N],m,d[N];
void sol() {
s.clear();
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) if(a[i]==m) a[i]=0;
for(int i=1;i<=n;i++) d[i]=a[i]-a[i-1];
int ans=0;
for(int i=1;i<=n;i++) {
if(d[i]==0) continue ;
// cout<<d[i]<<": ";
// if(!s.empty()) cout<<*s.begin();
// cout<<'\n';
if(d[i]<0) {
s.insert(d[i]);
} else {
if(s.empty()) {
ans+=d[i]; continue ;
}
int mi=*s.begin();
if(mi+m<=d[i]) ans+=mi+m,s.erase(s.begin()),s.insert(d[i]-m);
else ans+=d[i];
}
}
cout<<ans<<'\n';
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
int T; cin>>T; while(T--) sol();
return 0;
}