luogu P4064 [JXOI2017]加法 |二分+堆

题目描述

可怜有一个长度为 n 的正整数序列 A,但是她觉得 A 中的数字太小了,这让她很不开心。

于是她选择了 m 个区间 [li, ri] 和两个正整数 a, k。她打算从这 m 个区间里选出恰好 k 个区间,并对每个区间执行一次区间加 a 的操作。(每个区间最多只能选择一次。)

对区间 [l, r] 进行一次加 a 操作可以定义为对于所有 i ∈ [l, r],将 Ai 变成 Ai + a。现在可怜想要知道怎么选择区间才能让操作后的序列的最小值尽可能的大,即最大化min Ai

输入格式

第一行输入一个整数表示数据组数。

对于每组数据第一行输入四个整数 n, m, k, a。

第二行输入 n 个整数描述序列 A。

接下来 m 行每行两个整数 li, ri 描述每一个区间。数据保证所有区间两两不同。

输出格式

对于每组数据输出一个整数表示操作后序列最小值的最大值。


先二分这个最小值

然后用O(nlogn)判断 时间复杂度O(nlognlogn)

枚举每一个点,枚举到左边界的时候就把右边界加到堆里

这样的话,堆里的就是左节点在自己左侧的,然后再去取一下右节点,因为每次都是贪心考虑用最大的,所以是大根堆

不断取出最大右边界,把值加进去,直到满足二分的mid

再用个差分数组就可以避免区间修改使用线段树什么的

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+10;
inline int read(){
    int x=0; char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x;
}
int n,m,k,a,A[N],b[N];
struct node{
	int l,r;
}e[N];
inline bool cmp(node t1,node t2){
	return t1.l<t2.l;
}
priority_queue<int>q;
inline bool check(int mid){
	register int i;
	for(i=1;i<=n;i++)b[i]=A[i]-A[i-1];
	int num=1,ans=0;
	while(q.size())q.pop();
	for(i=1;i<=n;i++){
		b[i]+=b[i-1];
		while(e[num].l==i)q.push(e[num++].r);
		while(b[i]<mid&&q.size()&&q.top()>=i){
			b[i]+=a;
			b[q.top()+1]-=a;
			q.pop(); ans++;
		}
		if(b[i]<mid||ans>k)return 0;
	}
	return 1;
}
signed main(){
	register int i;
	for(int T=read(),mid;T;T--){
		n=read(); m=read(); k=read(); a=read();
		int l=1e9,r,ans=-1;
		for(i=1;i<=n;i++) A[i]=read(),l=min(l,A[i]); 
		for(i=1;i<=m;i++) e[i].l=read(),e[i].r=read(); 
		sort(e+1,e+m+1,cmp); r = l+m*a+1;
		while(l<=r){
			mid=(l+r)>>1;
			if(check(mid)){
				l=mid+1;
				ans=mid;
			}else r=mid-1;
		}
		
		printf("%d
",ans);
	}
}
原文地址:https://www.cnblogs.com/naruto-mzx/p/12008100.html