CDQ 分治
CDQ 分治
在二维偏序问题中,可以利用排序干掉一维,并用树状数组维护另一维.
在三维偏序中,就需要利用 cdq 分治,干掉一维,并用树状数组+排序维护剩下两维.
例题
1. 树状数组模板题
本来,这道题应该用树状数组来完成,不过可以利用本题练习 $\mathrm{cdq}$ 分治.
操作序列的顺序是一维,操作的位置是第二维.
利用 $\mathrm{cdq}$ 解决掉第一维,再利用排序解决第二维.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 500009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int val[N], cnt, n, m, scc;
struct data {
int ty, pos, d;
data(int ty=0,int pos=0,int d=0):ty(ty),pos(pos),d(d){}
bool operator<(const data b) const {
return pos == b.pos ? ty < b.ty : pos < b.pos;
}
}A[N * 3], tmp[N * 3];
ll ans[N];
void cdq(int l, int r) {
if(l >= r) {
return ;
}
int mid = (l + r) >> 1;
cdq(l, mid), cdq(mid + 1, r);
ll sum = 0ll;
int p = l, cn = 0, q = mid + 1;
while(p <= mid && q <= r) {
if(A[p] < A[q]) {
// 左面.
if(A[p].ty == 1) {
sum += A[p].d;
}
tmp[++cn] = A[p];
++ p;
}
else {
if(A[q].ty == 2) {
ans[A[q].d] += sum;
}
tmp[++cn] = A[q];
++ q;
}
}
while(p <= mid) {
tmp[++cn] = A[p];
p ++;
}
while(q <= r) {
if(A[q].ty == 2) ans[A[q].d] += sum;
tmp[++cn] = A[q];
q ++ ;
}
for(int i = 1; i <= cn ; ++i) {
A[l + i - 1] = tmp[i];
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d",&val[i]);
}
for(int i=1;i<=m;++i) {
int x, y, z, w, o;
scanf("%d", &o);
if(o == 1) {
scanf("%d%d%d",&x,&y,&z);
++ cnt;
A[cnt].ty = 1;
A[cnt].pos = x;
A[cnt].d = z;
if(y + 1 <= n) {
++ cnt;
A[cnt].ty = 1;
A[cnt].pos = y + 1;
A[cnt].d = -z;
}
}
else {
++ cnt;
A[cnt].ty = 2;
scanf("%d",&A[cnt].pos);
A[cnt].d = ++ scc;
ans[scc] = val[A[cnt].pos];
}
}
cdq(1, cnt);
for(int i=1;i<=scc;++i) {
printf("%lld\n", ans[i]);
}
return 0;
}
2.三维偏序(陌上花开)
利用之前提到的做法做就即可,用 $\mathrm{cdq}$ 分治干掉第一维,归并排序干掉第二维,树状数组搞第三维.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct BIT {
int C[N];
int lowbit(int x) {
return x & (-x);
}
void upd(int x, int v) {
while(x < N){
C[x] += v;
x += lowbit(x);
}
}
int query(int x) {
int tmp = 0;
while(x > 0) {
tmp += C[x];
x -= lowbit(x);
}
return tmp ;
}
void clr(int x) {
while(x < N) {
C[x] = 0;
x += lowbit(x);
}
}
}T;
int n,K,cnt;
ll ans[N], num[N];
struct data {
int x, y, z, k, id;
data(int x=0,int y=0,int z=0,int k=0,int id=0):x(x),y(y),z(z),k(k),id(id){}
}a[N], A[N], tmp[N], F[N];
bool cmp1(data i, data j) {
if(i.x == j.x && i.y == j.y) return i.z < j.z;
if(i.x == j.x) return i.y < j.y;
return i.x < j.x;
}
void cdq(int l, int r) {
if(l >= r) {
return ;
}
int mid = (l + r) >> 1;
cdq(l, mid), cdq(mid + 1, r);
// solve 完左面与右面.
int p = l, q = mid + 1, cn = 0;
while(p <= mid && q <= r) {
if(A[p].y <= A[q].y) {
T.upd(A[p].z, A[p].k);
tmp[++cn] = A[p];
++ p;
}
else {
ans[A[q].id] += T.query(A[q].z);
tmp[++cn] = A[q];
++ q;
}
}
while(p <= mid) {
tmp[++cn] = A[p];
++ p;
}
while(q <= r) {
ans[A[q].id] += T.query(A[q].z);
tmp[++cn] = A[q];
++q;
}
for(int i=1;i<=cn;++i) T.clr(tmp[i].z);
for(int i=1;i<=cn;++i) {
A[l+i-1]=tmp[i];
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&K);
for(int i=1;i<=n;++i) {
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
}
sort(a + 1, a + 1 + n, cmp1);
for(int i=1;i<=n;++i) {
if((a[i].x != a[i-1].x)||(a[i].y!=a[i-1].y)||(a[i].z!=a[i-1].z)) {
++ cnt;
A[cnt] = a[i];
A[cnt].k = 1;
A[cnt].id = cnt;
}
else {
++A[cnt].k;
}
}
for(int i=1;i<=cnt;++i) {
ans[i] = A[i].k - 1;
F[i] = A[i];
}
cdq(1, cnt);
for(int i=1;i<=cnt;++i) {
num[(int)ans[i]] += F[i].k ;
}
for(int i=0;i<n;++i) {
printf("%lld\n", num[i]);
}
return 0;
}
3.动态逆序对
来源:luogu3157
可以先求出初始情况逆序对,然后考虑每次删除所产生的贡献.
每次删除的贡献可以写成是一个三维数点的形式,这个就可以用 $\mathrm{CDQ}$ 分治维护了.
1. 操作顺序:最开始排个序就行.
2. 每个数字的位置:在 $\mathrm{CDQ}$ 分治时归并排序合并好.
3. 每个数字大小:利用树状数组来维护.
这样,三个维度就都被维护好了.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct BIT {
int C[N];
int lowbit(int x) {
return x & (-x);
}
void upd(int x, int v) {
while(x < N) {
C[x] += v;
x += lowbit(x);
}
}
int query(int x) {
int tmp = 0;
while(x > 0) {
tmp += C[x];
x -= lowbit(x);
}
return tmp;
}
void clr(int x) {
while(x < N) {
C[x] = 0;
x += lowbit(x);
}
}
}T;
struct data {
int t, pos, v;
data(int t=0,int pos=0,int v=0):t(t),pos(pos),v(v){}
}A[N << 1], tmp[N << 1];
int val[N], pos[N], del[N], det[N], n, m;
// 直接利用 t 贡献就行.
void cdq(int l, int r) {
if(l >= r) {
return ;
}
int mid = (l + r) >> 1;
cdq(l, mid), cdq(mid + 1, r);
// 按照操作顺序已经排好第一维了.
// 根据操作顺序,应该是大影响小的.
int cur = mid + 1;
for(int i = l; i <= mid ; ++ i) {
while(cur <= r && A[cur].pos < A[i].pos) {
T.upd(A[cur].v, 1);
++ cur;
}
det[A[i].t] += T.query(n) - T.query(A[i].v);
}
for(int i = mid + 1; i <= r; ++ i) T.clr(A[i].v);
cur = r;
for(int i = mid; i >= l; -- i) {
while(cur >= mid + 1 && A[cur].pos > A[i].pos) {
T.upd(A[cur].v, 1);
-- cur;
}
det[A[i].t] += T.query(A[i].v - 1);
}
for(int i = mid + 1; i <= r; ++ i) T.clr(A[i].v);
// 最后需要归并一下(按照pos排序)
int p0 = l, q0 = mid + 1, o = 0;
while(p0 <= mid && q0 <= r) {
if(A[p0].pos < A[q0].pos) {
tmp[++o] = A[p0];
++p0;
}
else {
tmp[++o] = A[q0];
++q0;
}
}
while(p0 <= mid) tmp[++o] = A[p0], ++p0;
while(q0 <= mid) tmp[++o] = A[q0], ++q0;
for(int i = 1; i <= o; ++ i) {
A[l + i - 1] = tmp[i];
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
ll sum = 0;
for(int i=1;i<=n;++i) {
scanf("%d",&val[i]);
pos[val[i]] = i;
sum += T.query(n) - T.query(val[i]);
T.upd(val[i], 1);
}
for(int i = 1; i <= n ; ++ i) T.clr(i);
for(int i=1;i<=n;++i) {
del[i] = n + 1;
}
int cnt = m;
for(int i=1;i<=m;++i) {
int x;
scanf("%d",&x);
del[x] = i;
A[i].t = i;
A[i].pos = pos[x];
A[i].v = x;
}
for(int i=1;i<=n;++i) {
if(del[i] > n) {
++cnt;
A[cnt].t = del[i];
A[cnt].pos = pos[i];
A[cnt].v = i;
}
}
cdq(1, cnt);
printf("%lld\n",sum);
for(int i=1;i<m;++i) {
sum -= det[i];
printf("%lld\n", sum);
}
return 0;
}

浙公网安备 33010602011771号