CF1852C Ina of the Mountain

发布时间 2023-09-06 19:53:24作者: FxorG

*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;
}