HDU - 1166 CDQ分治

题意:

树状数组的入门题,单点修改,区间求和。

思路:

这题是最基础的BIT,也可以用cdq分治来做。简单地介绍一下cdq分治。
cdq分治是一种特殊的分治法,只能支持离线操作,往往可以替代复杂的数据结构,而且具有常数较小的优点。
cdq分治的基本思想:
1. 对于一段序列[L,R);
2. 将其从中间分成两个部分[L,M),[M.R),并递归处理子问题。
3. 归并时要考虑左半部分[L,M)对于右半部份[M,R)的结果的影响。
cdq分治和普通分治最大的区别在于,普通分治在归并的时候左右两边是互不影响的,但是cdq分治中要考虑左半部分对于右半部份的影响。
举个典型的cdq分治的例子,就是归并排序。对于下标属于区间[L,R)中的数排序,那就按照上面的思想,先分成左右两个区间,并且递归处理,最后使得[L,M)和[M,R)这两个部分内部都变成有序的,然后在合并的时候,[L,M)这部分的数会对[M,R)这部分的数产生影响,需要下标p和q分别从L和M处开始遍历并相互比较,在临时数组tmp中排序,最后再把临时数组还原到原数组中。
依据同样的思想,cdq分治也可以解决二维,三维偏序问题。
除了以上所说的,cdq分治的一个很大的用途是用来解决有修改,查询的问题。把查询和修改都当作有序对来处理。

HDU-1166这题,我们把他转化成一个二维偏序问题,每个操作用一个有序对(a,b)表示,其中a表示操作到来的时间,b表示操作的位置,时间是默认有序的,所以我们在合并子问题的过程中,就按照b从小到大的顺序合并。

关键是如何表示查询和修改,用结构体Query统一标识查询和修改,其中包含三个元素,type,pos,val。其中查询[L,R]的和看作两个部分组成查询sum[R]和sum[L-1]。

1.type为1时表示修改操作,pos是修改的位置,val是添加的值。

2.type为2时表示针对左端点的前缀和查询,pos是左端点位置,val是该查询的编号,为了方便记录答案。

3.type为3时表示针对右端点的前缀和查询,pos是右端点位置,val是查询编号。

代码中的ans数组储存对于询问的答案。按照cdq分治的思路,因为各个操作的时间顺序已经默认,所以就直接将所有操作放入que数组中进行分治,为了方便讨论所有区间都是左闭右开,对于下标为[L,R)的区间,先分成[L,M)和[M,R)递归处理,然后归并,要考虑左部分的修改,对于右部分的查询造成的影响,对于本题来说就是左部分修改操作后的变化量和,对于查询操作,要查询的是sum[R]-sum[L],这里L位置之前的修改操作的变化量和add都会对其有影响,如果查询的pos是左端点,ans[id]要减去add,若是右端点,ans[id]要加上add。

具体细节需要到代码中领悟。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 50005;
const int MAXM = 40005;
const int MAXQ = MAXM * 2 + MAXN;

struct Query {
    int type, pos, val;
    bool operator < (const Query &rhs) const {
        return pos == rhs.pos ? type < rhs.type : pos < rhs.pos;  // 同一位置,修改操作要先于查询操作
    }
}que[MAXQ], tmp[MAXQ];

int ans[MAXQ];
int qnum, anum;     // qnum表示所有有序对的个数,anum表示询问操作的个数

void cdq(int l, int r) {
    if (l + 1 >= r) return;
    int m = (l + r) >> 1;
    cdq(l, m); cdq(m, r);
    int p = l, q = m, cnt = 0, sum = 0;
    while (p < m && q < r) {         // 类似归并排序的模式,换成了处理有序对
        if (que[p] < que[q]) {
            if (que[p].type == 1) sum += que[p].val;   // 左半部分先发生的修改操作,保存变化量的和sum
            tmp[cnt++] = que[p++];
        }
        else {
            if (que[q].type == 2) ans[que[q].val] -= sum;       // 左端点查询减去sum
            else if (que[q].type == 3) ans[que[q].val] += sum;      // 右端点查询加上sum
            tmp[cnt++] = que[q++];
        }
    }
    while (p < m) tmp[cnt++] = que[p++];
    while (q < r) {
        if (que[q].type == 2) ans[que[q].val] -= sum;
        else if (que[q].type == 3) ans[que[q].val] += sum;
        tmp[cnt++] = que[q++];
    }
    for (int i = 0; i < cnt; i++)       // 利用临时数组更新操作数组que
        que[i + l] = tmp[i];
}

char op[10];

int main() {
    //freopen("in.txt", "r", stdin);
    int T, cs = 0;
    scanf("%d", &T);
    while (T--) {
        int n, x;
        scanf("%d", &n);
        qnum = anum = 0;
        for (int i = 1; i <= n; i++, qnum++) {
            scanf("%d", &x);
            que[qnum] = (Query) {1, i, x};
        }
        while (true) {
            scanf("%s", op);
            if (op[0] == 'E') break;
            if (op[0] == 'Q') {
                int l, r;
                scanf("%d%d", &l, &r);
                que[qnum++] = (Query) {2, l - 1, anum};
                que[qnum++] = (Query) {3, r, anum++};
            }
            else {
                int pos, add;
                scanf("%d%d", &pos, &add);
                add *= (op[0] == 'A' ? 1 : -1);
                que[qnum++] = (Query) {1, pos, add};
            }
        }
        memset(ans, 0, sizeof(ans));      // 记得ans清零
        cdq(0, qnum);
        printf("Case %d:\n", ++cs);
        for (int i = 0; i < anum; i++)
            printf("%d\n", ans[i]);
    }
    return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值