HDU 5412 CRB and Queries (整体二分)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5412

题目大意:对一个数组A[N](N<= 10^5)进行两种操作:

  1 l v:将第A[l]的值修改成v

  2 l r k:求l~r区间的第k小值

操作总数为10^5.

2015年多校第十场的题目,当时乱套主席树什么的模板,发现并不能过,赛后学习了一种叫做整体二分的方法,感觉很是厉害。

整体二分是二分答案。大致方法如下:

  1、先把所有对数组的操作保存起来,包括赋值。

  2、从0~inf二分一个数值,并以此数值为界,将操作划分成两部分

  3、递归处理

细节有点不好说,还是看代码比较清楚。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 150100
#define inf 1000000100
struct Que{
    int tp;
    int x, y, k, id, cur;
}Q[N<<1], Q1[N<<1], Q2[N<<1];
int qcnt;
int a[N];

int tr[N];
int ans[N];
int m;
inline void add(int id, int v){
    for(int i = id; i < N; i += (i&-i))
        tr[i] += v;
}
inline int sum(int id){
    int ret = 0;
    for(int i = id; i > 0; i -= (i&-i))
        ret += tr[i];
    return ret;
}

int tmp[N];
void divide(int s, int t, int l, int r)
{
    if(s > t) return;
    if(l == r)
    {
        for(int i = s; i <= t; i++)
            if(Q[i].tp == 2)
                ans[Q[i].id] = l;
        return;
    }
    int mid = (l+r)>>1;
    for(int i = s; i <= t; i++)
    {
        if(Q[i].tp == 0 && Q[i].y <= mid) add(Q[i].x, 1);
        else if(Q[i].tp == 1 && Q[i].y <= mid) add(Q[i].x, -1);
        else if(Q[i].tp == 2) tmp[Q[i].id] = sum(Q[i].y) - sum(Q[i].x-1);
    }
    //还原 
    for(int i = s; i <= t; i++)
    {
        if(Q[i].tp == 0 && Q[i].y <= mid) add(Q[i].x, -1);
        else if(Q[i].tp == 1 && Q[i].y <= mid) add(Q[i].x, 1);
    }
    //下面是根据当前二分的答案mid,将操作划分成两部分 
    int l1 = 0, l2 = 0;
    for(int i = s; i <= t; i++)
    {
        if(Q[i].tp == 2){
            if(Q[i].cur+tmp[Q[i].id] >= Q[i].k) Q1[l1++] = Q[i];
            else {
                Q[i].cur += tmp[Q[i].id];
                Q2[l2++] = Q[i];
            }
        }
        else{
            if(Q[i].y <= mid) Q1[l1++] = Q[i];
            else Q2[l2++] = Q[i];
        }
    }
    for(int i = 0; i < l1; i++) Q[s+i] = Q1[i];
    for(int i = 0; i < l2; i++) Q[s+l1+i] = Q2[i];
    divide(s, s+l1-1, l, mid);
    divide(s+l1, t, mid+1, r);
}

int main()
{
    int n, q;
    while(~scanf("%d", &n))
    {
        qcnt = 0;
        int Max = -1;
        int Min = inf;
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            Max = max(Max, a[i]);
            Min = min(Min, a[i]);
            //赋值也要看成是操作 
            Q[qcnt].x = i, Q[qcnt].y = a[i], Q[qcnt].tp = 0;
            Q[qcnt++].cur = 0;
        }
        scanf("%d", &q);
        int id = 0;
        for(int i = 0; i < q; i++)
        {
            int op, x, y, k;
            scanf("%d", &op);
            if(op == 1){
                scanf("%d %d", &x, &y);
                //to == 1表示这个值是要被修改的,减去当前值,再加上新值,就完成了修改 
                //cur的作用:若当前的答案二分区间是 [l, r],则cur保存在Q[i].l, Q[i].r范围之内,有多少个小于 l 的数 
                Q[qcnt].x = x, Q[qcnt].y = a[x], Q[qcnt].tp = 1, Q[qcnt++].cur = 0;
                Q[qcnt].x = x, Q[qcnt].y = y, Q[qcnt].tp = 0, Q[qcnt++].cur = 0;
                a[x] = y;
                Max = max(Max, y);
                Min = min(Min, y);
            }
            else {
                scanf("%d %d %d", &x, &y, &k);
                Q[qcnt].x = x, Q[qcnt].y = y, Q[qcnt].k = k, Q[qcnt].tp = 2;
                Q[qcnt].id = id++, Q[qcnt++].cur = 0;
            }
        }
        //树状数组每次add后还会被还原,就没必要每次初始化了 
        //memset(tr, 0, sizeof(tr));
        divide(0, qcnt-1, Min, Max);
        for(int i = 0; i < id; i++)
            printf("%d
", ans[i]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/beisong/p/4756829.html