整体二分——K大数查询

本文深入探讨了整体二分算法的概念及其在解决特定问题中的应用,并详细解析了树状数组的工作原理,通过实例演示如何利用树状数组进行高效的数据处理。

Description

这里的加入是把每个元素看成一个集合!!!

Solution

这道题主要是讲一下整体二分

整体二分顾名思义,就是有一坨数,规定一个 midmidmid 进行二分。(好像一点也不清楚)

思考一下普通的二分,我们其实是帮助一个数值 ansansans 选择一个合适的值。整体二分就是帮助很多个数值选择一个合适的值。

接下来说一下算法流程了吧。

我们定义两个序列 LLLRRR。对于此题我们的 midmidmid 就是假定CCC 大的数,那么对于这次遍历大于 midmidmid 的数就是能对结果产生影响的,设目前为止大于 midmidmid 的数的个数为 numnumnum

首先我们的时间戳是有序的,这个就不用排序了。对于每个询问与插入,我们可以进行分类讨论。

  • 操作为询问,numnumnum 小于询问要求的 CCC。这说明我们把 midmidmid 的值调大了,我们将 CCC 减去 numnumnum,再在 [l,mid][l,mid][l,mid] 中选择新值(即插入 LLL)。(注意这里的 midmidmid 是可以取到的,因为判断条件是 num<Cnum < Cnum<C

  • 操作为询问,numnumnum 大于等于要求的 CCC。我们先前判断用的是严格大于才加入树状数组,所以等于的情况不会触及 midmidmid。从 [mid+1,r][mid+1,r][mid+1,r] 中找就行了。

  • 操作为插入,C>midC>midC>mid。这种权值插入 RRR,再在树状数组中更新就行了。

  • 操作为插入,C<=midC<=midC<=mid。这种权值不满足条件,只能与第一种操作进行配对,插入 LLL 就行了。

看到树状数组也许你看不懂(当然你可以写线段树,比树状数组好理解得多,我是因为我是 250250250 才会去做的 QwQQwQQwQ),接下来我会详细讲解树状数组,如果没有看懂可以私信或评论哟!(蒟蒻求赞

先来一张图:(这个可以结合代码观看,这个部分感觉网上有些代码可能有问题就比如我看的那份树状数组代码)

我们在 222 这个节点加了 111 这个数值,用另一个数组加了 2−1==12-1==121==1 这个数值。

接下来假设我们 ask(x==5)ask(x==5)ask(x==5)。因为这相当于是一个前缀和:对于 c1[]c1[]c1[],记录的就是在 [1,x][1,x][1,x] 之间有多少个添加的 111(注意这里也可以改成 −1-11,这个部分我就不讲了,一通百通)。而对于 c2[]c2[]c2[],记录的就是每个位置上被添加的次数 * (位置 - 1)。(注意这里的两个数组已经完成 askaskask 中的累加

那么 ∑c1∗x−∑c2\sum c1*x-\sum c2c1xc2 对应在图上就是一条长的橙色的线段减去短的橙色的线段,正好就是黄色的线段:这个 111xxx 的距离。

总结一下:这个 askaskask 函数返回的值就是 xxx 与之前每个位置的距离 * 这个位置被添加 111 的个数。

好的我们再放一张图,来解释最终的答案。

ask(L−1)=5−2+1=4ask(L-1)=5-2+1=4ask(L1)=52+1=4ask(R)=(8−2+1)+(8−7+1)=9ask(R)=(8-2+1)+(8-7+1)=9ask(R)=(82+1)+(87+1)=9

相减即为所求:在 [L,R][L,R][L,R] 区间内大于 midmidmid 的数的个数。

我竟然写完了 QwQQwQQwQ。(希望不要被布布扣转载小声哔哔)

Code

#include <cstdio>
#define int long long

const int N = 5e4 + 5;
int n, m, p[N], ans[N], op[N], l[N], r[N], c1[N], c2[N], c[N], lef[N], rig[N];

int read() {
    int x = 0, f = 1; char s;
    while((s = getchar()) < '0' || s > '9') if(s == '-') f = -1;
    while(s >= '0' && s <= '9') {x = (x << 1) + (x << 3) + (s ^ 48); s = getchar();}
    return x * f;
}

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

void add(const int x, const int k) {
    for(int i = x; i <= n; i += lowbit(i))
        c1[i] += k, c2[i] += k * (x - 1);
}

int ask(const int x) {
    int r = 0;
    for(int i = x; i; i -= lowbit(i))
        r += c1[i] * x - c2[i];
    return r;
}

void cdq(const int L, const int R, const int down, const int up) {
    if(L > R || down > up) return;
    if(down == up) {for(int i = L; i <= R; ++ i) ans[p[i]] = up; return;}
    int mid = up + down >> 1; int lenl = 0, lenr = 0;
    for(int i = L; i <= R; ++ i) {
        int pos = p[i];
        if(op[pos] & 2) {
            int num = ask(r[pos]) - ask(l[pos] - 1);
            if(num < c[pos]) lef[++ lenl] = pos, c[pos] -= num;
            else rig[++ lenr] = pos;
        }
        else {
            if(c[pos] > mid) add(l[pos], 1), add(r[pos] + 1, -1), rig[++ lenr] = pos;
            else lef[++ lenl] = pos;
        }
    }
    for(int i = 1; i <= lenl; ++ i) p[L + i - 1] = lef[i];
    for(int i = 1; i <= lenr; ++ i) {
        p[L + i + lenl - 1] = rig[i];
        if(op[p[L + i + lenl - 1]] & 1) add(l[p[L + i + lenl - 1]], -1), add(r[p[L + i + lenl - 1]] + 1, 1);
    }
    cdq(L, L + lenl - 1, down, mid); cdq(L + lenl, R, mid + 1, up);
}

signed main() {
    n = read(), m = read();
    for(int i = 1; i <= m; ++ i)
        p[i] = i, op[i] = read(), l[i] = read(), r[i] = read(), c[i] = read();
    cdq(1, m, -n, n);
    for(int i = 1; i <= m; ++ i)
        if(op[i] & 2) printf("%lld\n", ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值