iinattr

万字长文·三维偏序/cdq分治

2022-07-24

万字长文·三维偏序/cdq分治

cdq分治

静态区间问题

cdq分治是一种解决区间统计问题的常用方法

点分治相当于吧cdq分治挪到树上

cdq分治即把一个序列分为两部分 区间统计的量分为跨中点的 左边的 右边的

(一下所说统计均指在区间中点)

先递归处理左边的和右边的

然后再寻找某种$O(n)$或较优方式统计跨中点的

熟知归并排序求逆序对

这就是使用cdq分治解决二维偏序的例子

离线的带修数据结构问题

cdq 分治被称为 动态问题转化为静态问题的工具

发现离线的数据结构离线下来的操作是一个序列

每个修改操作会对它后面的询问操作产生影响

使用cdq分治来完成这个问题

先递归处理[l,mid],[mid+1,r]

然后加入[l,mid]中操作对[mid+1,r]中询问的影响 并得出答案

正确性:显然所有修改按时间顺序排序

一个例子

矩形加矩形求和¶
这里的矩形加矩形求和就是字面意思上的矩形加矩形求和,
让你维护一个二维平面,然后支持在一个矩形区域内加一个数字,
每次询问一个矩形区域的和

这个问题的不带修就是经典的扫描线问题

加入修改后只需按照cdq套路完成即可qwq

三维偏序

显然三维偏序问题是二维偏序的扩展

二维偏序有两种写法:归并排序(cdq分治)和树状数组

考虑结合二者

先对第一维排序

然后显然[l,mid]和[mid+1,r]只会在两者之间产生偏序

我们可以使用双指针扫左区间和右区间 同二维偏序一样

将左区间第二维小于右区间的第三维插入树状数组 将对应于左区间的右区间首个b<左区间点的点的c值在树状数组中查询前缀和

正确性与归并排序同理

细节:对于相同点 显然二者可以互相贡献答案 而该算法显然只能计算一次 所以要去重再把重值算回

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include<bits/stdc++.h>
using namespace std;
#define N 200005
struct node
{
long long a,b,c,cnt,ans=0;
}q1[N],q2[N];
long long n,kk,asn[N];
bool cmp(node x,node y)
{
if(x.a==y.a)
{
if(x.b==y.b)
return x.c<y.c;
else
return x.b<y.b;
}
else return x.a<y.a;
}
bool cmp2(node x,node y)
{
if(x.b==y.b)
return x.c<y.c;
else
return x.b<y.b;
}
struct tyz
{
long long tr[N];
void change(long long x,long long k)
{
for(;x<=kk;x+=(x&(-x)))
tr[x]+=k;
}
long long ask(long long x)
{
long long ret=0;
for(;x>0;x-=(x&(-x)))
ret+=tr[x];
return ret;
}
void clear()
{
for(long long i=1;i<=kk;i++) tr[i]=0;
}
}qt;
void cdq(long long l,long long r)
{
if(l==r) return;
long long mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
long long ls=l,rs=mid+1;long long i=l;
for(;i<=r&&ls<=mid&&rs<=r;)
{
if(q2[ls].b<=q2[rs].b)
qt.change(q2[ls].c,q2[ls].cnt),
q1[i].a=q2[ls].a,
q1[i].b=q2[ls].b,
q1[i].c=q2[ls].c,
q1[i].ans=q2[ls].ans,
q1[i].cnt=q2[ls].cnt,
i++,
ls++;
else
q2[rs].ans+=qt.ask(q2[rs].c),
q1[i].a=q2[rs].a,
q1[i].b=q2[rs].b,
q1[i].c=q2[rs].c,
q1[i].ans=q2[rs].ans,
q1[i].cnt=q2[rs].cnt,
i++,
rs++;
}
while(rs<=r)
{
q2[i].ans+=qt.ask(q2[rs].c),
q1[i].a=q2[rs].a,
q1[i].b=q2[rs].b,
q1[i].c=q2[rs].c,
q1[i].ans=q2[rs].ans,
q1[i].cnt=q2[rs].cnt,
i++;
rs++;
}
for(int i=l;i<ls;i++)
qt.change(q2[i].c,-q2[i].cnt);
while(ls<=mid)
{
q1[i].a=q2[ls].a,
q1[i].b=q2[ls].b,
q1[i].c=q2[ls].c,
q1[i].ans=q2[ls].ans,
q1[i].cnt=q2[ls].cnt,
i++;
ls++;
}
for(long long kkk=l;kkk<=r;kkk++)
{
q2[kkk].a=q1[kkk].a,
q2[kkk].b=q1[kkk].b,
q2[kkk].c=q1[kkk].c,
q2[kkk].ans=q1[kkk].ans,
q2[kkk].cnt=q1[kkk].cnt;
}

}
int main()
{
scanf("%d%d",&n,&kk);
for(long long i=1;i<=n;i++)
scanf("%d%d%d",&q1[i].a,&q1[i].b,&q1[i].c);
sort(q1+1,q1+1+n,cmp);
long long m=0,top=0;
for(long long i=1;i<=n;i++)
{
top++;
if(q1[i].a!=q1[i+1].a||q1[i].b!=q1[i+1].b||q1[i].c!=q1[i+1].c)
{
m++;
q2[m].a=q1[i].a;
q2[m].b=q1[i].b;
q2[m].c=q1[i].c;
q2[m].cnt=top;
top=0;
}
}
cdq(1,m);
for(long long i=1;i<=m;i++)
asn[q2[i].ans+q2[i].cnt-1]+=q2[i].cnt;
for(long long i=0;i<n;i++)
cout<<asn[i]<<endl;
return 0;
}