CDQ分治
多用于解决多维偏序问题,通过增加 l o g ( n ) log(n) log(n) 的时间将偏序问题降一维。离线算法
对于修改和询问问题,修改操作的位置小于某一询问时,修改才会对询问产生影响。将操作顺序视为第一维,操作位置视为第二维,值视为第三维,实际上也是三维偏序问题。
CDQ分治大致有两个基本形式:多维偏序降维,动态修改转换为静态修改统一查询
将整个序列分为前一半和后一半,可以发现
- 后一半序列中的修改不会对前一半的序列产生影响
- 后一半序列中的 x x x ,只会受到前一半序列 和 后一半序列 x x x 之前的操作影响
算法的关键:将两个处理完的区间合并时,考虑左半区间对右半区间造成的影响
【模板】三维偏序(陌上花开)
题目:
有
n
n
n 个元素,第
i
i
i个元素有
a
i
,
b
i
,
c
i
a_i,b_i,c_i
ai,bi,ci三个属性、设
f
(
i
)
f(i)
f(i)表示满足
a
j
≤
a
i
a_j \le a_i
aj≤ai 且
b
j
≤
b_j\le
bj≤ 且
c
j
≤
c
i
c_j \le c_i
cj≤ci 的数量。
求
f
(
i
)
=
d
f(i) = d
f(i)=d 的数量。
(
0
≤
d
<
n
)
(0 \le d < n)
(0≤d<n)
解析:
三维偏序。
设三维分别为 x , y , z x, y, z x,y,z 对应题目中的 a , b , c a, b, c a,b,c 。先按照 x x x 进行排序。在计算前区间对后区间的影响时,分别按照 y y y 对前、后区间进行排序。按照 y y y 排序之后,在前区间中, x x x 变成无序的,后区间同理。但前区间的 x x x 小于后区间中 x x x。
维护两个指针 i , j i,j i,j , i i i指向前区间的元素, j j j 指向后区间的元素。 每次将 j j j 后移一位,如果有 y [ i ] < = y [ j ] y[i] <= y[j] y[i]<=y[j] 则不断后移 i i i ,并将 z [ i ] z[i] z[i] 加入树状数组,查询树状数组中有多少元素小于等于 z [ i ] z[i] z[i]。最后清空树状数组
- 清空数组的时候不能用memset
- 注意相同元素的处理
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;
int c[maxn], k;
int lowbit(int x){return x&(-x);}
void add(int pos, int v){
for(; pos <= k; pos += lowbit(pos))
c[pos] += v;
}
ll query(int pos){
ll res = 0;
while(pos){
res += c[pos];
pos -= lowbit(pos);
}
return res;
}
struct node{
int a, b, c;
int cnt, res;
node(){a = b = c = cnt = res = 0;}
node(int a, int b, int c) : a(a), b(b), c(c), cnt(0), res(0){}
bool operator != (const node &y) const{
return a!=y.a || b!=y.b || c!=y.c;
}
}s1[maxn], s2[maxn];
bool cmpa(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;
}
return x.a < y.a;
}
bool cmpb(node x, node y){
if(x.b == y.b)
return x.c < y.c;
else
return x.b < y.b;
}
int tot;
int ans[maxn];
void cdq(int l, int r){
if(l == r)
return;
int mid = (l+r) >> 1;
cdq(l, mid); cdq(mid+1, r);
sort(s2+l, s2+mid+1, cmpb);
sort(s2+mid+1, s2+r+1, cmpb);
int i = l;
for(int j = mid+1; j <= r; j++){
while(s2[j].b >= s2[i].b && i <= mid){
add(s2[i].c, s2[i].cnt);
i++;
}
s2[j].res += query(s2[j].c);
}
for(int j = l; j < i; j++)
add(s2[j].c, -s2[j].cnt);
}
void solve(){
int n;
cin >> n >> k;
for(int i = 1; i <= n; i++){
int a, b, c;
cin >> a >> b >> c;
s1[i] = node(a, b, c);
}
sort(s1+1, s1+1+n, cmpa);
int num = 0;
for(int i = 1; i <= n; i++){
num++;
if(s1[i] != s1[i+1]){
s2[++tot] = s1[i];
s2[tot].cnt = num;
num = 0;
}
}
cdq(1, tot);
for(int i = 1; i <= tot; i++)
ans[s2[i].cnt-1+s2[i].res] += s2[i].cnt;
for(int i = 0; i < n; i++)
cout << ans[i] << endl;
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T = 1;
while(T--)
solve();
return 0;
}
P3374 【模板】树状数组 1
题意:
单点修和区间询问
解析:
将操作排列成一个序列,递归处理左右子区间。合并两个区间时,考虑左区间中的修改对右区间中的询问造成的影响
查询区间和可以转化成查询前缀和,即 q ( l , r ) = s u m ( r ) − s u m ( l − 1 ) q(l,r) = sum(r) - sum(l-1) q(l,r)=sum(r)−sum(l−1)
初值也看成修改操作,对整个操作序列进行CDQ分治
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
const int maxn = 2e6+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;
struct query{
int type;
int id, val;
query(){}
query(int type, int id, int val) : type(type), id(id), val(val){}
bool operator < (const query &b)const {
if(id == b.id)
return type < b.type;
else
return id < b.id;
}
}q[maxn], t[maxn];
int ans[maxn];
void cdq(int l, int r){
if(l == r)
return;
int mid = (l+r) >> 1;
cdq(l, mid); cdq(mid+1, r);
int sum = 0, tcnt = l;
int i = l, j = mid+1;
while(i <= mid && j <= r){
if(q[i] < q[j]){
if(q[i].type == 1) //单点修
sum += q[i].val;
t[tcnt++] = q[i++];
}
else{
if(q[j].type == 2)
ans[q[j].val] -= sum;
if(q[j].type == 3)
ans[q[j].val] += sum;
t[tcnt++] = q[j++];
}
}
while(i <= mid)
t[tcnt++] = q[i++];
while(j <= r){
if(q[j].type == 2)
ans[q[j].val] -= sum;
if(q[j].type == 3)
ans[q[j].val] += sum;
t[tcnt++] = q[j++];
}
for(int i = l; i <= r; i++)
q[i] = t[i];
}
int tot, qid;
int n, m;
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
int x; cin >> x;
q[++tot] = query(1, i, x);
}
for(int i = 1; i <= m; i++){
int op, x, y;
cin >> op >> x >> y;
if(op == 1)
q[++tot] = query(1, x, y);
else{
q[++tot] = query(2, x-1, ++qid);
q[++tot] = query(3, y, qid);
}
}
cdq(1, tot);
for(int i = 1; i <= qid; i++)
cout << ans[i] << endl;
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T = 1;
while(T--)
solve();
return 0;
}