【贪心+堆+树状数组】JXOI2017[加法]题解

来源:互联网 发布:ei数据库免费检索入口 编辑:程序博客网 时间:2024/06/17 09:03

题目概述

有一个序列 {an}m 个区间,你可以选 k 个区间,选择区间 [l,r] 可以使 a 中的 [l,r] 都加上 A ,找出一个方案使得 a 中最小值最大。

解题报告

由于是求最小值的最大值,所以我们想到二分答案 mid ,这样我们就知道序列中每个数还需要加多少次才能 mid ,设第 i 个数的次数为 ti[i]

那么接下来我们要做的就是验证是否能用 k 个线段覆盖掉 ti 数组,这个可以用贪心解决:从左往右枚举 i ,对于 i 我们需要 ti[i] 个满足条件的线段来覆盖,什么样的线段比较优秀呢?肯定是右端点远的线段!所以我们用堆维护这些满足条件的线段,每次从中选出右端点最远的线段使用,并将 ti 数组进行相应的修改(这个可以用树状数组解决)。如果使用的线段数量 k 就说明验证成功。

示例程序

2017.8.15Update:由于代码有些小技巧,加了点注释。

#include<cstdio>#include<algorithm>using namespace std;#define Fir first#define Sec secondconst int maxn=200000,maxm=200000;int te,n,m,K,A,a[maxn+5];pair<int,int> S[maxm+5];inline bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}inline char readc(){    static char buf[100000],*l=buf,*r=buf;    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);    if (l==r) return EOF; else return *l++;}inline int readi(int &x){    int tot=0,f=1;char ch=readc(),lst='+';    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=readc();}    if (lst=='-') f=-f;    while ('0'<=ch&&ch<='9') tot=(tot<<3)+(tot<<1)+ch-48,ch=readc();    return x=tot*f,Eoln(ch);}int c[maxn+5];void Update(int x,int tem) {for (int p=x;p<=n;p+=p&-p) c[p]+=tem;}int Sum(int x) {int sum=0;for (int p=x;p;p-=p&-p) sum+=c[p];return sum;}int si,Heap[maxn+5];bool check(int MIN){    si=0;for (int i=1;i<=n;i++) c[i]=0;    for (int i=1,j=1,k=K;i<=n;i++)    {        if (MIN-a[i]<=0) continue;int ti=(MIN-a[i]-1)/A+1-Sum(n-i+1);if (ti<=0) continue;        //树状数组求后缀和:把位置取反,转化为前缀和        while (j<=m&&S[j].Fir<=i) Heap[++si]=S[j].Sec,push_heap(Heap+1,Heap+1+si),j++;        //push_heap和手写的效果是一样的,听说在开O2的情况下跑得比手写快        //push_heap(begin,end)表示将begin到end调整为大根堆(要保证begin到end-1是大根堆)        while (si&&k&&ti&&Heap[1]>=i)        {            Update(n-Heap[1]+1,1);pop_heap(Heap+1,Heap+1+si);            si--;k--;ti--;        }        //pop_heap同理        if (ti) return false;    }    return true;}int main(){    freopen("add.in","r",stdin);    freopen("add.out","w",stdout);    for (readi(te);te;te--)    {        readi(n);readi(m);readi(K);readi(A);        int L=1,R=0;        for (int i=1;i<=n;i++) readi(a[i]),R=max(R,a[i]);        for (int i=1,x,y;i<=m;i++)        {            readi(x);readi(y);if (x>y) swap(x,y);            S[i]=make_pair(x,y);        }        sort(S+1,S+1+m);R+=A*K;        while (L<=R)        {            int mid=L+(R-L>>1);            if (check(mid)) L=mid+1; else R=mid-1;        }        printf("%d\n",L-1);    }    return 0;}
原创粉丝点击