CDQ分治

C D Q CDQ CDQ 分治与普通的分治不同, C D Q CDQ CDQ 分治分的是时间(即操作的顺序),所以 C D Q CDQ CDQ 分治是一种离线算法,需要把操作全部存下来。同时两个操作之间不能互相影响,这与普通分治是相同的。

C D Q CDQ CDQ 分治的算法流程与归并类似:用归并解决一段数列中的逆序对的数量其实就相当于 C D Q CDQ CDQ
归并解决逆序对:对当前数列进行归并排序,在两段区间合并之前,先统计每个右区间中的元素有多少比左区间的元素小,之后再合并左右区间。
比如一段序列 3 , 5 , 10 , 2 , 6 , 9 , 7 , 8 , 1 3 , 5 , 10 , 2 , 6 , 9 , 7 , 8 , 1 3510269781
可以把每个数字当作一个操作(x,y)(在x位置上插入y):(1,3),(2,5),(3,10),…,(9,1)。所以就是求有多少对操作 x i &lt; x j x_i&lt;x_j xi<xj y i &gt; y j y_i&gt;y_j yi>yj。但是在求逆序对的时候 x x x 默认为升序的了,所以可以省去 x x x 。但如果是一般的求二维偏序(即 x x x 不是默认升序的),需要把数组按 x x x 排序,就能转换成常见的逆序对求解。

再扩展一下问题,把二维偏序对转换为三维偏序对:
例题:洛谷 P3810
这题就是求三维的逆序对,这里我们可以把第一维 x x x 当作操作的时间,整个数组按照 x , y , z x,y,z xyz 的优先级排序,对 x x x 进行分治,这里对一个区间进行分析。

对于区间 [ L , R ] [L,R] [LR],按照 m i d mid mid 分成左右区间,按照归并的流程(该归并按照 y y y 进行排序),显然左区间和右区间分别以 y y y 有序,但整个右区间元素里的 x x x 均大于左区间的,所以可以扫描右区间,把左区间所有 y y y 小于右区间当前的 y y y 的加入一个新的数组,所以每次这个数组里存着这个整个大区间里 x , y x,y xy 都小于当前左区间这个元素的 x , y x,y xy ,那么统计 z z z 也满足条件的个数就可以交给树状数组。

PS:由于存在完全相同的元素,根据 C D Q CDQ CDQ 的流程,只会统计到前面的那个元素对于后面元素,然而相同的元素的话,前面的元素同时也会计算上后面的,所以需要把相同的元素合并起来。

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define NUM 100005
int n, k;
int t[NUM << 1];
struct QUERY
{
    int num, x, y, z, w;
} q[NUM];
int ans[NUM << 1] = {};
inline int lowbit(const int &x){return (x & (-x));}
inline void add(int id, const int &x)
{
    do
    {
        t[id] += x;
        id += lowbit(id);
    } while (id <= k);
}
inline int get(int id)
{
    int sum = 0;
    do
    {
        sum += t[id];
        id -= lowbit(id);
    } while (id);
    return sum;
}
inline bool cmpx(const QUERY &a, const QUERY &b)
{
    if (a.x != b.x)
        return a.x < b.x;
    if (a.y != b.y)
        return a.y < b.y;
    return a.z < b.z;
}
inline bool cmpy(const QUERY &a, const QUERY &b)
{
    if (a.y != b.y)
        return a.y < b.y;
    return a.z < b.z;
}
void cdq(const int &l, const int &r)
{
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    cdq(l, mid), cdq(mid + 1, r);
    sort(q + l, q + mid + 1, cmpy), sort(q + mid + 1, q + r + 1, cmpy);//按照y排序
    int j = l;
    for (int i = mid + 1; i <= r; ++i)//统计右区间的i对应左区间中有多少j(这时候右区间的x还是满足均大于左区间的x)
    {
        while (j <= mid && q[j].y <= q[i].y)
        {
            add(q[j].z, q[j].num);
            ++j;
        }
        q[i].w += get(q[i].z);
    }
    for (int i = l; i < j; ++i)
        add(q[i].z, -q[i].num);
}
int main()
{
    cin >> n >> k;
    int m = 0, tmp = 0;
    for (int i = 1; i <= n; ++i)
    {
        cin >> q[i].x >> q[i].y >> q[i].z;
        q[i].w = 0;
    }
    sort(q + 1, q + 1 + n, cmpx);
    for (int i = 1; i <= n; ++i)
    {//完全相等情况下,两个元素互相均会统计,然而cdq分治中只会统计前者对后者的影响,所以需要合并相同的元素
        ++tmp;
        if (q[i].x != q[i + 1].x || q[i].y != q[i + 1].y || q[i].z != q[i + 1].z)
        {
            q[++m] = q[i];
            q[m].num = tmp;
            tmp = 0;
        }
    }
    cdq(1, m);
    for (int i = 1; i <= m; ++i)
        ans[q[i].w + q[i].num - 1] += q[i].num;
    for (int i = 0; i < n; ++i)
        cout << ans[i] << '\n';
    return 0;
}

例题:BZOJ 1176
单点修改+区间查询,每次修改一个点的值,查询一个矩阵的和。对于查询矩阵和的操作,可以分成四个操作,求(0,0,x1-1,y1-1),(0,0,x1-1,y2),(0,0,x2,y1-1),(0,0,x2,y2)四个矩形和。而操作顺序即是时间,直接分治时间,再按照 x x x 进行归并排序,中途 y y y 同样按照树状数组维护。

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define NUM 200005
#define MAXN 2000005
#define ll long long
int W, Q = 0;
ll t[MAXN] = {}, S;
struct QUERY
{
    int x, y, type, id;
    ll w;
    bool operator>(const QUERY &other)const
    {
        if (x != other.x)
            return x > other.x;
        if (y != other.y)
            return y > other.y;
        return type > other.type;
    }
} q[NUM], a[NUM];
ll ans[10005];
template <typename T>
void read(T& x)
{
    x=0;
    char c;T t=1;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c=='-'){t=-1;c=getchar();}
    do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
    x*=t;
}
template <typename T>
void write(T x)
{
    int len=0;char c[21];
    if(x<0)putchar('-'),x*=(-1);
    do{++len;c[len]=(x%10)+'0';}while(x/=10);
    _ffor(i, len, 1) putchar(c[i]);
}
inline int lowbit(const int &x){return (x & (-x));}
inline void add(int id, const ll &x)
{
    do
    {
        t[id] += x;
        id += lowbit(id);
    } while (id <= W);
}
inline ll get(int id)
{
    ll sum = 0;
    do
    {
        sum += t[id];
        id -= lowbit(id);
    } while (id);
    return sum;
}
void cdq(const int &l, const int &r)
{
    if (l == r)return;
    int mid = (l + r) >> 1;
    cdq(l, mid), cdq(mid + 1, r);
    int i, j, k;
    for (i = mid + 1, j = l; i <= r; ++i)
    {
        while (j <= mid && q[j].x <= q[i].x)
        {
            if (q[j].type == 1)add(q[j].y, q[j].w);
            ++j;
        }
        if (q[i].type == 3) ans[q[i].id] += get(q[i].y);
        else if (q[i].type == 2) ans[q[i].id] -= get(q[i].y);
    }
    for (i = l; i < j; ++i)if (q[i].type == 1)add(q[i].y, -q[i].w);
    i = l, j = mid + 1, k = 0;
    while (i <= mid || j <= r)
    {
        if (i > mid){a[++k] = q[j++];continue;}
        if (j > r){a[++k] = q[i++];continue;}
        if (q[i] > q[j])a[++k] = q[j++];
        else a[++k] = q[i++];
    }
    for (i = l, j = 1; i <= r; ++i, ++j)q[i] = a[j];
}
inline void init(const int &x, const int &y, const int &type, const int &id, const ll &w)
{
    if (x == 0 || y == 0)return;
    q[++Q] = QUERY{x, y, type, id, w};
}
int main()
{
    read(S), read(W);
    int m = 0, op, x1, x2, y1, y2;
    ll a;
    while (read(op), op != 3)
    {
        read(x1), read(y1);
        if (op == 1)
        {
            read(a), init(x1, y1, op, 0, a);
            continue;
        }
        ++m;
        read(x2), read(y2);
        init(x1 - 1, y1 - 1, 3, m, 0), init(x1 - 1, y2, 2, m, 0), init(x2, y1 - 1, 2, m, 0), init(x2, y2, 3, m, 0);
        ans[m] = 1LL * (1LL + x2 - x1) * (1LL + y2 - y1) * S;
    }
    cdq(1, Q);
    ffor(i, 1, m)
        write(ans[i]), putchar('\n');
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值