「JXOI2017」加法题解

题解:

主要思路:

(1)首先要想到贪心,若想要满足最小数最大,那么我们尽量选区间长度长的修改方法修改,这样同是加a,但是长度长的加的更多,明显比较优越。
(2)如何找到最大区间,我们可以在输入后对其左端点排序(原因等会说),令最小的左端点排在前面,然后要用时再依次插入优先队列,而优先队列即是要用来将左端点满足条件且最大区间排在前面
(3)我们可以二分枚举最小的最大值,然后再从1——n循环,如果a[i]还小于mid,就说明还未到最大,可以继续修改区间使其最大,如果大于或等于就说明a[i]经过修改区间后不是最大的最小值,跳到下一个a[i], 如果最后都没有,就加大mid。
(4)这里要用到区间修改,其次每修改一次还要查询最小值,所以是树状数组的区间修改,单点查询

细节处理:

(1)排序:

首先排序时如果判到两个数的左端点相同,那么就按右端点排,反正就把区间大的放前。同样优先队列的运算符重载也一样,如果右端点一样,就把左端点最小的放前面。

int cmp(jj x, jj y) {  //按左端点最小的排
    if (x.qi == y.qi)
        return x.zhong > y.zhong;  //起点相同按终点排
    else
        return x.qi < y.qi;  //按起点排
}

struct node2 {  //定义优先队列,实现每次拿出的右端点都最大
    int l, r;
    bool operator<(const node2 &x) const {
        if (r != x.r) {
            return r < x.r;  //右端点排序
        } else {
            return l < x.l;
        }
    }
};

(2)为何排左端点:

while (o[j].qi <= i && j <= m) {
            node2 rr;
            rr.l = o[j].qi;
            rr.r = o[j].zhong;
            q.push(rr);
            j++;
 }

如上方代码我们可以知道,我们的起点必须要小于等于i,因为这样才可以令每次改变都可以改变第i个数,从而达到第i个数一定最大,而按左端点排后,如果当前j号方法起点不满足<=i,那么后面的方法一定也不行(因为左端点从小到大排过),所以直接跳出循环。

(3)二分的左右端点:

最小值假设最大的最小值不会改变受区间任何改变,那么就是 a [ 1 — n ] a[1—n] a[1n]最小值。最大值假设每一次都要改变最大的最小值,那么则是 a [ 1 — n ] a[1—n] a[1n]最小值 + k * a(即k次它都加了a)

 long long l = 1, r = 0x3f3f3f3f;
 for (int i = 1; i <= n; i++) {
     scanf("%d", &A[i]);
     r = min(r, (long long)A[i]);
     l = min(l, (long long)A[i]);
 }
r = r + a * k;

(3)还有就是在循环cheak里面的一些细节,详细看注释:

bool cheak(long long s) {
    memset(BIT, 0, sizeof(BIT));
    for (int i = 1; i <= n; i++) {
        update(i, A[i] - A[i - 1]);  //差分数组插入
    }
    priority_queue<node2> q;  // q放入的是目前要改变的区间,此区间满足最大
    int ans = 0;//改变了几个区间
    int j = 1;//选第几个改变区间方法
    for (int i = 1; i <= n; i++) {
        while (o[j].qi <= i && j <= m) {//如果j号改变区间方法可以改现在a[i]的且还没把方法枚举完就插入
            node2 rr;
            rr.l = o[j].qi;
            rr.r = o[j].zhong;
            q.push(rr);  //因为o数组已经按照l排序,剩下再用重载让l最小且r最大的放前面,最后得到的是满组条件且最大区间
            j++;
        }
        while (Sum(i) < s) {  //如果此时最小值小于mid则证明可继续修改,如果大于或等于了则证明mid取小了,要增大
            if (q.empty() || ans == k) {  //若无区间可查或已经改变了k个区间了,就证明mid不可取跳出
                return 0;
            }
            node2 ll = q.top();
            q.pop();
            update(ll.l, a);
            update(ll.r + 1, -a);
            ans++;//改变次数++
        }
    }
    return 1;
}

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;
int n, m, k, a, A[200005], _max, BIT[200005];
struct jj {
    int qi, zhong;  //起始位置,终止位置
} o[200005];

int cmp(
    jj x,
    jj y) {  //因为越大的区间可修改的数越多,即可让改变的数最多,最小值最容易增大,所以用区间大小从大到小改
    if (x.qi == y.qi)
        return x.zhong > y.zhong;  //起点相同按终点排
    else
        return x.qi < y.qi;  //暂时先让起点最大的排在前面,之后再用“优先队列”排终点,使区间最大
}

struct node2 {  //定义优先队列,实现每次拿出的右端点都最大
    int l, r;
    bool operator<(const node2 &x) const {
        if (r != x.r) {
            return r < x.r;  //右端点排序
        } else {
            return l < x.l;
        }
    }
};

int lowbit(int x) { return x & (-x); }

void update(int k, int shu) {
    for (int i = k; i <= n; i += lowbit(i)) {
        BIT[i] += shu;
    }
}

long long Sum(int zhong) {
    long long da = 0;
    for (int i = zhong; i >= 1; i -= lowbit(i)) {
        da += BIT[i];
    }
    return da;
}

bool cheak(long long s) {
    memset(BIT, 0, sizeof(BIT));
    for (int i = 1; i <= n; i++) {
        update(i, A[i] - A[i - 1]);  //差分数组插入
    }
    priority_queue<node2> q;  // q放入的是目前要改变的区间,此区间满足最大
    int ans = 0;
    int j = 1;
    for (int i = 1; i <= n; i++) {
        while (o[j].qi <= i && j <= m) {
            node2 rr;
            rr.l = o[j].qi;
            rr.r = o[j].zhong;
            q.push(rr);  //因为o数组已经按照l排序,剩下再用重载让l最小且r最大的放前面,最后得到的是最大区间
            j++;
        }
        while (Sum(i) < s) {  //如果此时最小值小于mid则证明可继续修改,如果大于或等于了则证明mid取小了,要增大
            if (q.empty() || ans == k) {  //若无区间可查或已经改变了k个区间了,就证明mid不可取
                return 0;
            }
            node2 ll = q.top();
            q.pop();
            update(ll.l, a);
            update(ll.r + 1, -a);
            ans++;
        }
    }
    return 1;
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d %d %d %d", &n, &m, &k, &a);
        long long l = 1, r = 0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &A[i]);
            r = min(r, (long long)A[i]);
            l = min(l, (long long)A[i]);
        }
        r = r + a * k;
        for (int i = 1; i <= m; i++) {
            scanf("%d %d", &o[i].qi, &o[i].zhong);
        }
        sort(o + 1, o + 1 + m, cmp);
        while (l <= r) {
            long long mid = (l + r) / 2;
            if (cheak(mid)) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        printf("%lld\n", l - 1);
    }
    return 0;
}

你可能感兴趣的:(树状数组,二分)