分治
经典分治
个人写的第一个分治就是归并排序,当时啥都不懂只以为是一个很精妙的排序算法。将大区间的排序变成两个有序区间的合并,不断分割下去。
紧接着,知道了归并排序可以用来求逆序对。
归并排序在合并答案的时候,如果右区间有一个数字 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,x−1]的前缀和,以及 [ 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;
}