「JXOI2017」加法
题目描述
可怜有一个长度为 $n$ 的正整数序列 $A$ ,但是她觉得 $A$ 中的数字太小了,这让她很不开心。
于是她选择了 $m$ 个区间 $[l_i , r_i]$ 和两个正整数 $a$ , $k$ 。她打算从这 $m$ 个区间里选出恰好 $k$ 个区间,并对每个区间执行一次区间加 $a$ 的操作。
(每个区间最多只能选择一次。)
对区间 $[l, r]$ 进行一次加 $a$ 操作可以定义为对于所有 $i \in [l, r]$ ,将 $A_i$ 变成 $A_i + a$ 。
现在可怜想要知道怎么选择区间才能让操作后的序列的最小值尽可能的大,即最大化 $\min \{ A_i \}$ 。
数据范围
$\sum n \leq 2 \times 10^5,\sum m \leq 2 \times 10^5$
题解
看到最小值最大化首先想到二分答案,然后贪心。
从 $1$ 到 $n$ 考虑每个 $a_i$ 能否达到 $mid$ 。如果没有达到的话,我们可以选择一些区间使它变大。
那我们应该选择怎样的区间呢?不难发现应该选择右端点更远的区间,这样对后面的 $a$ 来说更容易达到 $mid$
所以我们可以把区间按照左端点排序,进行 $two-pointer$ ,用堆维护右端点即可。
代码
#include <bits/stdc++.h> #define LL long long using namespace std; const int N=2e5+5; int T,n,m,k; LL f[N],in,a,s[N]; struct O{int l,r;}p[N]; bool cmp(O A,O B){return A.l<B.l;} bool J(LL x){ priority_queue<int>q; for (int i=1;i<=n;i++) s[i]=f[i]-f[i-1]; for (int j=1,r,i=1,l=0;i<=n;i++){ while(j<=m && p[j].l<=i) q.push(p[j++].r); s[i]+=s[i-1]; while(!q.empty() && s[i]<x && l<k){ r=q.top();q.pop(); if (r<i) break; s[i]+=a;l++;s[r+1]-=a; } if (s[i]<x) return 0; } return 1; } void work(){ scanf("%d%d%d%lld",&n,&m,&k,&a);in=2e18; for (int i=1;i<=n;i++) scanf("%lld",&f[i]),in=min(f[i],in); for (int i=1;i<=m;i++) scanf("%d%d",&p[i].l,&p[i].r); sort(p+1,p+m+1,cmp); LL l=in,r=in+a*k,mid; while(l<r){ mid=(l+r+1)>>1; if (J(mid)) l=mid; else r=mid-1; } printf("%lld\n",l); } int main(){ for (scanf("%d",&T);T--;work()); return 0; }