bzoj 5321(二分+优先队列+线段树)
传送门
题意:
给你一个长度为的数组, 以及个连续的区间。现在让你取恰好个区间,你要将你选取的个区间都加上。现在要你最大化整个数组的最小值,即要最大化
题解:
最大化最小值,看到这样的词汇就非常二分了,于是乎我们就考虑采用二分答案解决。
于是乎,我们现在需要考虑的就是如何进行
我们首先考虑哪些如何选取这个区间才能使得我们的结果最优。一个贪心的选法就是,选择的区间一定要覆盖最小值的区间,同时区间的大小要尽可能的大。
因此我们可以考虑先将区间根据左端点的大小排序,然后当我们二分出一个答案的时候,我们先遍历整个数组,而在遍历的过程中,我们把能够包涵该端点的所有区间(即)都压入一个容器中。而若当前的位置的值小于我们所二分的答案,则我们考虑使用容器中的区间进行更新。而根据我们之前所说的贪心的选法,我们明显需要优先选取右端点距离远的区间,因此我们此时我们只需要用一个优先队列对符合条件的区间进行维护即可。
最后,倘若我们发现没有符合条件的区间,亦或者我们选取的区间个数大于,则证明当前二分值过大,此时我们只需要将左移即可。
最后,上述算法也仅涉及区间更新以及单调查询,因此我们可以用线段树或者差分树状数组进行维护。整体的时间复杂度为:
代码:
- 线段树版本:
#include <bits/stdc++.h>
#define maxn 200005
using namespace std;
typedef long long ll;
typedef pair<int,int>pll;
struct ST{
ll sum,lazy;
int len;
}tr[maxn<<2];
int A[maxn];
void push_up(int rt){
tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum;
}
void push_down(int rt){
if(tr[rt].lazy){
tr[rt<<1].sum+=tr[rt].lazy*tr[rt<<1].len;
tr[rt<<1|1].sum+=tr[rt].lazy*tr[rt<<1|1].len;
tr[rt<<1].lazy+=tr[rt].lazy;
tr[rt<<1|1].lazy+=tr[rt].lazy;
tr[rt].lazy=0;
}
}
void build(int l,int r,int rt){
tr[rt].len=r-l+1;
tr[rt].lazy=0;
if(l==r){
tr[rt].sum=A[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
push_up(rt);
}
void update(int L,int R,int l,int r,int rt,int C){
if(L<=l&&R>=r){
tr[rt].lazy+=C;
tr[rt].sum+=C*tr[rt].len;
return ;
}
int mid=(l+r)>>1;
push_down(rt);
if(L<=mid) update(L,R,l,mid,rt<<1,C);
if(R>mid) update(L,R,mid+1,r,rt<<1|1,C);
push_up(rt);
}
ll query(int L,int R,int l,int r,int rt){
if(L<=l&&R>=r){
return tr[rt].sum;
}
int mid=(l+r)>>1;
push_down(rt);
ll res=0;
if(L<=mid) res+=query(L,R,l,mid,rt<<1);
if(R>mid) res+=query(L,R,mid+1,r,rt<<1|1);
return res;
}
priority_queue<pll>que;
pll q[maxn];
int n,m,k,a;
bool judge(ll x){
build(1,n,1);
while(!que.empty()) que.pop();
int cnt=1;
int tt=0;
for(int i=1;i<=n;i++){
while(cnt<=m&&q[cnt].first<=i){
que.push(pll(q[cnt].second,q[cnt].first));
cnt++;
}
while(query(i,i,1,n,1)<x){
if(que.empty()) return false;
pll p=que.top();
que.pop();
if(p.first<i) return false;
int l=p.second,r=p.first;
//cout<<"// "<<l<<" "<<r<<endl;
update(l,r,1,n,1,a);
tt++;
if(tt>k) return false;
}
}
return true;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d%d%d",&n,&m,&k,&a);
int minn=0x3f3f3f3f;
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
minn=min(minn,A[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].first,&q[i].second);
}
sort(q+1,q+1+m);
ll l=minn,r=minn+k*a;
while(l<r){
ll mid=(l+r)>>1;
//cout<<l<<" "<<r<<" "<<mid<<endl;
if(judge(mid)) l=mid+1;
else r=mid;
}
if(judge(l)) printf("%lld\n",l);
else printf("%lld\n",l-1);
}
}
- 差分树状数组:
#include <bits/stdc++.h>
#define maxn 200005
using namespace std;
typedef long long ll;
typedef pair<int,int>pll;
int A[maxn];
ll bit[maxn<<1];
priority_queue<pll>que;
pll q[maxn];
ll lowbit(ll x){
return x&-x;
}
void update(int pos,ll C){
while(pos<maxn){
bit[pos]+=C;
pos+=lowbit(pos);
}
}
ll query(int pos){
ll res=0;
while(pos){
res+=bit[pos];
pos-=lowbit(pos);
}
return res;
}
int n,m,k,a;
bool judge(ll x){
memset(bit,0,sizeof(ll)*(n+3));
while(!que.empty()) que.pop();
int cnt=1;
int tt=0;
for(int i=1;i<=n;i++){
update(i,A[i]);
update(i+1,-A[i]);
}
for(int i=1;i<=n;i++){
while(cnt<=m&&q[cnt].first<=i){
que.push(pll(q[cnt].second,q[cnt].first));
cnt++;
}
while(query(i)<x){
if(que.empty()) return false;
pll p=que.top();
que.pop();
if(p.first<i) return false;
int l=p.second,r=p.first;
update(l,a);
update(r+1,-a);
tt++;
if(tt>k) return false;
}
}
return true;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d%d%d",&n,&m,&k,&a);
int minn=0x3f3f3f3f;
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
minn=min(minn,A[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].first,&q[i].second);
}
sort(q+1,q+1+m);
ll l=minn,r=minn+k*a;
while(l<r){
ll mid=(l+r)>>1;
if(judge(mid)) l=mid+1;
else r=mid;
}
if(judge(l)) printf("%lld\n",l);
else printf("%lld\n",l-1);
}
}