您的位置:首页 > 其它

[P4064][JXOI2017]加法(贪心+树状数组+堆)

2018-04-12 11:07 281 查看

题目描述

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

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

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

输入输出格式

输入格式:

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

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

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

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

输出格式:

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

输入输出样例

输入样例#1: 复制
1
3 3 2 1
1 3 2
1 1
1 3
3 3
输出样例#1: 复制
3

说明

选择给区间 [1, 1] 和 [1, 3] 加 1。

对于100%的数据,保证1≤n,m≤200,1\leq n,m \leq 200, 1≤n,m≤200, 1≤T≤2000,1≤k≤m,1≤a≤100,1≤Ai≤1081\leq T\leq 2000, 1 ≤ k ≤ m, 1 ≤ a ≤ 100, 1 ≤ A_i ≤ 10^81≤T≤2000,1≤k≤m,1≤a≤100,1≤Ai​≤108

不要相信数据范围,应该是$1\leq n,m \leq 10^5$。

二分答案,贪心扫一遍,对于小于二分值的位置,用堆找到包含它且向右延伸最长的区间加上它,区间加用差分树状数组实现。

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=l; i<=r; i++)
using namespace std;

const int N=200100;
priority_queue<int>Q;
int T,n,m,k,A,q
,a
,c
,stk
;
struct P{ int l,r; }p
;
bool cmp(P a,P b){ return a.l<b.l; }
void add(int x,int k){ for (; x<=n+1; x+=x&-x) c[x]+=k; }
int que(int x){ int res=0; for (; x; x-=x&-x) res+=c[x]; return res; }

bool chk(int lim){
while (!Q.empty()) Q.pop();
memset(c,0,sizeof(c)); int top=0,j=1,cnt=0;
rep(i,1,n) if (a[i]<lim) stk[++top]=i;
rep(i,1,top){
while (j<=m && p[j].l<=stk[i]) Q.push(p[j].r),j++;
while (a[stk[i]]+que(stk[i])<lim){
cnt++; if (cnt>k || Q.empty()) return 0;
int x=Q.top(); Q.pop(); add(stk[i],A); add(x+1,-A);
}
}
return 1;
}

int main(){
freopen("P4064.in","r",stdin);
freopen("P4064.out","w",stdout);
for (scanf("%d",&T); T--; ){
scanf("%d%d%d%d",&n,&m,&k,&A);
rep(i,1,n) scanf("%d",&a[i]);
rep(i,1,m) scanf("%d%d",&p[i].l,&p[i].r);
sort(p+1,p+m+1,cmp);
int l=1,r=120000000,mid,ans;
while (l<=r){
mid=(l+r)>>1;
if (chk(mid)) ans=mid,l=mid+1; else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: