P4064 [JXOI2017] 加法 题解

P4064 [JXOI2017] 加法

Description

给你一个长度为 \(n\) 的正整数序列 \(A\),再给你 \(m\) 个区间,让你在这 \(m\) 个区间中选出 \(k\) 个进行区间加 \(a\) 操作(\(a\) 为常数),使得 $\min{{A_i}}$ 最大化。

Solution

又是经典的最小值最大化,考虑二分这个最小值。

注意到最小值一定在 \([\min{A_i},\min{A_i}+m\times a]\) 这个范围内,直接二分即可。

如何 check 呢?

我们从前到后扫一遍 \(A_i\),如果发现有 \(A_i<\text{mid}\),就考虑用区间覆盖 \(A_i\)。对于这类用区间覆盖点的题,一个常用的 trick 是用右端点尽量靠右的区间来包含这个点。那么我们就可以先对右端点排序,再用大根堆来维护。

现在需要的是一个支持区间加单点求值的数据结构,考虑使用 Vector 😃

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long T,n,m,k,s,a[200005],l,r,ans,d[200005];
vector<int>e[200005];
bool check(){
	priority_queue<int> q;
	int u=0,v=0,m=(l+r)/2;
	memset(d,0,sizeof(d));
	for(int i=1;i<=n;i++){
		for(int j:e[i]){
			q.push(j);
		}
		v+=d[i];
		while(a[i]+v<m){
			u++;
			if(q.empty()||q.top()<i||u>k){
				r=m-1;
				return 0;
			}
			v+=s;
			d[q.top()+1]-=s;
			q.pop();
		}
	}
	l=m+1;
	ans=m;
	return true;
}
signed main(){
	cin>>T;
	while(T--){
		cin>>n>>m>>k>>s;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			e[i].clear();
		}
		for(int i=1;i<=m;i++){
			cin>>l>>r;
			e[l].push_back(r);
		}
		l=0,r=1e9;
		ans=0;
		while(l<=r){
			check();
		}
		cout<<ans<<endl;
	}
	return 0;
}
posted @ 2025-12-09 22:03  Creativexz  阅读(2)  评论(0)    收藏  举报