CDQ分治笔记(持续更新)

CDQ分治

即对于每个i,有多少个j使得 a j < = a i a_j<=a_i aj<=ai, b j < = b i b_j<=b_i bj<=bi, c j < = c i c_j<=c_i cj<=ci

一维:

排序即可

二维:

①:树状数组解决即可
值域统计后插入元素即可
②:分治
加双关键字排序(便于处理 a j < = a i , b j < = b i a_j<=a_i,b_j<=b_i aj<=ai,bj<=bi)的情况
e g eg eg 逆序对:
①:i,j都在左半边
②:i,j都在右半边
③:j在左,i在右
①和②递归即可,至于③,考虑满足第二维的限制即可,使用双指针算法,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

三维

①:三关键字排序
②:将满足i,j要求的数对分成三大类
A:i,j均在左半边(递归求)
B:i,j均在右半边(递归求)
C:j在左,i在右
按B排序
对 于 每 个 i , 通 过 双 指 针 找 到 b i < = b j 的 位 置 , 则 在 1 至 j 的 所 有 数 里 找 到 满 足 < = c i 的 数 的 个 数 ( 树 状 数 组 ) 对于每个i, 通过双指针找到bi<=bj的位置, 则在1至j的所有数里找到满足<=c_i的数的个数(树状数组) i,bi<=bj1j<=ci

时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

备注:对于 a i = = a j , b i = = b j , c i = = c j a_i==a_j,b_i == b_j,c_i == c_j ai==aj,bi==bj,ci==cj的情况,去除掉,并记录个数,最后在答案加上(个数-1)

模板题
代码+详细注释

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, M = 200010;

int n, m;
struct Data
{
    int a, b, c, s, res;

    bool operator< (const Data& t) const
    {
        if (a != t.a) return a < t.a;
        if (b != t.b) return b < t.b;
        return c < t.c;
    }
    bool operator== (const Data& t) const
    {
        return a == t.a && b == t.b && c == t.c;
    }
}q[N], w[N];
int tr[M], ans[N];

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

void add(int x, int v)
{
    for (int i = x; i < M; i += lowbit(i)) tr[i] += v;
}

int query(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

void merge_sort(int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r)
        if (q[i].b <= q[j].b) add(q[i].c, q[i].s), w[k ++ ] = q[i ++ ];
        else q[j].res += query(q[j].c), w[k ++ ] = q[j ++ ];
    while (i <= mid) add(q[i].c, q[i].s), w[k ++ ] = q[i ++ ];
    while (j <= r) q[j].res += query(q[j].c), w[k ++ ] = q[j ++ ];
    for (i = l; i <= mid; i ++ ) add(q[i].c, -q[i].s);
    for (i = l, j = 0; j < k; i ++, j ++ ) q[i] = w[j];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        q[i] = {a, b, c, 1};
    }
    sort(q, q + n);

    int k = 1;
    for (int i = 1; i < n; i ++ )
        if (q[i] == q[k - 1]) q[k - 1].s ++ ;
        else q[k ++ ] = q[i];

    merge_sort(0, k - 1);
    for (int i = 0; i < k; i ++ )
        ans[q[i].res + q[i].s - 1] += q[i].s;

    for (int i = 0; i < n; i ++ ) printf("%d\n", ans[i]);

    return 0;
}

应用

一:老C的任务
思路:考虑转化为CDQ分治
这对于每个点,求出其作为左上顶点的矩形中所包含的点的权值之和。(即二维前缀和)
而zi只有1,0两种取值,不需使用树状数组,使用一个变量维护即可。
代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 500010;

int n, m;
struct Data
{
    int x, y, z, p, id, sign;
    LL sum;

    bool operator< (const Data& t) const
    {
        if (x != t.x) return x < t.x;
        if (y != t.y) return y < t.y;
        return z < t.z;
    }
}q[N], w[N];
LL ans[N];

void merge_sort(int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    LL sum = 0;
    while (i <= mid && j <= r)
        if (q[i].y <= q[j].y) sum += !q[i].z * q[i].p, w[k ++ ] = q[i ++ ];
        else q[j].sum += sum, w[k ++ ] = q[j ++ ];
    while (i <= mid) sum += !q[i].z * q[i].p, w[k ++ ] = q[i ++ ];
    while (j <= r) q[j].sum += sum, w[k ++ ] = q[j ++ ];
    for (i = l, j = 0; j < k; i ++, j ++ ) q[i] = w[j];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        int x, y, p;
        scanf("%d%d%d", &x, &y, &p);
        q[i] = {x, y, 0, p};
    }
    int k = n;
    for (int i = 1; i <= m; i ++ )
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        q[k ++ ] = {x2, y2, 1, 0, i, 1};
        q[k ++ ] = {x1 - 1, y2, 1, 0, i, -1};
        q[k ++ ] = {x2, y1 - 1, 1, 0, i, -1};
        q[k ++ ] = {x1 - 1, y1 - 1, 1, 0, i, 1};
    }

    sort(q, q + k);
    merge_sort(0, k - 1);

    for (int i = 0; i < k; i ++ )
        if (q[i].z)
            ans[q[i].id] += q[i].sum * q[i].sign;

    for (int i = 1; i <= m; i ++ ) printf("%lld\n", ans[i]);
    return 0;
}

二:动态逆序对

思路:现在已经有两维,第三维为时间戳,即当前该数被删除的时间
需要记录
图片
代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m;
struct Data
{
    int a, t, res;
}q[N], w[N];
int tr[N], pos[N];
LL ans[N];

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

void add(int x, int v)
{
    for (int i = x; i < N; i += lowbit(i)) tr[i] += v;
}

int query(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

void merge_sort(int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    int i = mid, j = r;
    while (i >= l && j > mid)
        if (q[i].a > q[j].a) add(q[i].t, 1), i -- ;
        else q[j].res += query(q[j].t - 1), j -- ;
    while (j > mid) q[j].res += query(q[j].t - 1), j -- ;
    for (int k = i + 1; k <= mid; k ++ ) add(q[k].t, -1);

    j = l, i = mid + 1;
    while (j <= mid && i <= r)
        if (q[i].a < q[j].a) add(q[i].t, 1), i ++ ;
        else q[j].res += query(q[j].t - 1), j ++ ;
    while (j <= mid) q[j].res += query(q[j].t - 1), j ++ ;
    for (int k = mid + 1; k < i; k ++ ) add(q[k].t, -1);

    i = l, j = mid + 1;
    int k = 0;
    while (i <= mid && j <= r)
        if (q[i].a <= q[j].a) w[k ++ ] = q[i ++ ];
        else w[k ++ ] = q[j ++ ];
    while (i <= mid) w[k ++ ] = q[i ++ ];
    while (j <= r) w[k ++ ] = q[j ++ ];

    for (i = l, j = 0; j < k; i ++, j ++ ) q[i] = w[j];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        scanf("%d", &q[i].a);
        pos[q[i].a] = i;
    }
    for (int i = 0, j = n; i < m; i ++ )
    {
        int a;
        scanf("%d", &a);
        q[pos[a]].t = j -- ;
        pos[a] = -1;
    }

    for (int i = 1, j = n - m; i <= n; i ++ )
        if (pos[i] != -1)
            q[pos[i]].t = j -- ;

    merge_sort(0, n - 1);

    for (int i = 0; i < n; i ++ ) ans[q[i].t] = q[i].res;
    for (int i = 2; i <= n; i ++ ) ans[i] += ans[i - 1];
    for (int i = 0, j = n; i < m; i ++, j -- ) printf("%lld\n", ans[j]);

    return 0;
}

引用文献

一、肖然的博客
二、算法进阶课

希望做一点微小的贡献
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值