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

题目概述

有一个序列 {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
#include
using namespace std;
#define Fir first
#define Sec second
const 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''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;
}

你可能感兴趣的:(堆,树状数组,一般贪心)