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

浙公网安备 33010602011771号