题解 P4064 【[JXOI2017]加法】
emmm……我参加了JXOI2016和JXOI2018,就是没参加2017(滚去中考了),真奇妙
考虑二分答案。二分答案之后,问题就变成了:选择不超过k个区间来操作,使得所有数都不小于mid
于是我们可以将所有区间按左端点排序,然后从左到右扫描,扫到一个点,就将左端点位于该点左侧的线段全部加入一个容器中。若该点小于mid,我们就需要在容器中拿一个区间来操作,这个点前面的所有点已经大于等于mid了,所以我们的目的是影响后面尽可能多的点,因此右端点更靠右的区间是更优的。
这样其实就确定了我们要使用的容器是堆,是一个以右端点为关键字的大根堆
若某一个数小于mid,然而堆顶元素的右端点都在mid左边了,显然check就失败了
现在我们还需要一个能支持区间加法、单点查询的数据结构,那就是树状数组。再统计一下区间加法的次数,超过k次就返回false
#include<bits/stdc++.h>
#define FR first
#define SE second
using namespace std;
const int S=(1<<20)+5;
char buf[S],*H,*T;
inline char Get()
{
if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
if(H==T) return -1;return *H++;
}
inline int read()
{
int x=0;char c=Get();
while(!isdigit(c)) c=Get();
while(isdigit(c)) x=x*10+c-'0',c=Get();
return x;
}
typedef pair<int,int> pii;
typedef long long LL;
const int N=200010;
pii seg[N];
int n,m,k,a;
int A[N];
LL bit[N];
priority_queue<pii> pq;
inline int lowbit(const int &x){return x&-x;}
void add(int p,int x){for(;p<=n+1;p+=lowbit(p))bit[p]+=x;}
LL qry(int p){LL res=0;for(;p;p-=lowbit(p))res+=bit[p];return res;}
bool check(int x)
{
memset(bit,0,sizeof(LL)*(n+3));
for(int i=1;i<=n;i++)
add(i,A[i]),add(i+1,-A[i]);
int p=1,ans=0;
while(!pq.empty()) pq.pop();
for(int i=1;i<=n;i++)
{
while(p<=m&&seg[p].FR<=i) pq.push(pii(seg[p].SE,seg[p].FR)),p++;
while(qry(i)<x)
{
if(pq.empty()) return 0;
pii t=pq.top();pq.pop();
if(t.FR<i) return 0;
add(t.SE,a);add(t.FR+1,-a);
if(++ans>k) return 0;
}
}
return 1;
}
int main()
{
for(int T_T=read();T_T;T_T--)
{
int l=INT_MAX;
n=read();m=read();k=read();a=read();
for(int i=1;i<=n;i++) l=min(l,A[i]=read());
for(int i=1;i<=m;i++) seg[i].FR=read(),seg[i].SE=read();
sort(seg+1,seg+1+m);
int r=l+k*a,mid;
while(l<=r)
{
mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid-1;
}
printf("%d\n",check(l)?l:l-1);
}
return 0;
}