分治小结

分治

经典分治

​ 个人写的第一个分治就是归并排序,当时啥都不懂只以为是一个很精妙的排序算法。将大区间的排序变成两个有序区间的合并,不断分割下去。

​ 紧接着,知道了归并排序可以用来求逆序对。

​ 归并排序在合并答案的时候,如果右区间有一个数字 a a a,小于左区间的一个数字 b b b,那么左区间所有在 b b b后面的数字都会和a一起对答案做出贡献。

CDQ分治

​ 实际上归并排序就是CDQ分治,CDQ分治的思想就是统计左区间对右区间的影响。

二维偏序

​ 逆序对实际上就是二维偏序问题, A i = v a l A_i = val Ai=val被视为点 ( i , v a l ) (i,val) (i,val),那就是对于所有的点,求出第一维比他小,第二维比他大的点的个数。

特殊的二维偏序(将操作顺序转化为时间维度)

​ 为啥CDQ分治可以搞很多需要数据结构的题呢?看下面的例题。

给定序列A,操作1,x,y,将x位置元素加上y,操作2,x,y,查询[x,y]区间和

​ 这是个树状数组的裸题,但我们就是试着用CDQ分治做,CDQ分治可以解决二维偏序,那么我们尝试把这题转换成二维偏序。

​ 我们定义序列A的初始化操作均为操作1,对于操作2我们用前缀和的思想进行拆分,即拆成查询 [ 1 , x − 1 ] [1,x-1] [1,x1]的前缀和,以及 [ 1 , y ] [1,y] [1,y]的前缀和,相减即可,对于每个操作,我们将其改写 ( i , x , y , z ) (i,x,y,z) (i,x,y,z),表示第 i i i个操作,操作是针对元素x的(可能是查询也可能是增加), yz是附加信息,例如增加多少之类的。

​ 那么对于每一个查询操作 ( i , x ) (i,x) (i,x),能对他产生影响的只有在查询之前的修改以及修改的位置在 x x x之前的,那么就可以变成二维偏序,cdq分治,对于右区间的所有查询,左区间的修改将对他们产生影响,将影响累加即可。
代码来源于网络

#include <cstdio>
#include <cstring>
#include <iostream>
#define ll long long

using namespace std;

const int N = 5000010;
int n, m, totx = 0, tot = 0; //totx是操作的个数,tot询问的编号

struct node
{
    int type, id;
    ll val;
    bool operator<(const node &a) const //重载运算符,优先时间排序
    {
        if (id != a.id)
            return id < a.id;
        else
            return type < a.type;
    }
};
node A[N], B[N];
ll ans[N];

void CDQ(int L, int R)
{
    if (L == R)
        return;
    int M = (L + R) >> 1;
    CDQ(L, M);
    CDQ(M + 1, R);
    int t1 = L, t2 = M + 1;
    ll sum = 0;
    for (int i = L; i <= R; i++)
    {
        if ((t1 <= M && A[t1] < A[t2]) || t2 > R) //只修改左边区间内的修改值
        {
            if (A[t1].type == 1)
                sum += A[t1].val; //sum是修改的总值
            B[i] = A[t1++];
        }
        else //只统计右边区间内的查询结果
        {
            if (A[t2].type == 3)
                ans[A[t2].val] += sum;
            else if (A[t2].type == 2)
                ans[A[t2].val] -= sum;
            B[i] = A[t2++];
        }
    }
    for (int i = L; i <= R; i++)
        A[i] = B[i];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        tot++;
        A[tot].type = 1;
        A[tot].id = i; //修改操作
        scanf("%lld", &A[tot].val);
    }
    for (int i = 1; i <= m; i++)
    {
        int t;
        scanf("%d", &t);
        tot++;
        A[tot].type = t;
        if (t == 1)
            scanf("%d%lld", &A[tot].id, &A[tot].val);
        else
        {
            int l, r;
            scanf("%d%d", &l, &r);
            totx++;
            A[tot].val = totx;
            A[tot].id = l - 1; //询问的前一个位置
            tot++;
            A[tot].type = 3;
            A[tot].val = totx;
            A[tot].id = r; //询问的后端点
        }
    }
    CDQ(1, tot);
    for (int i = 1; i <= totx; i++)
        printf("%lld\n", ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值