算法笔记-CDQ分治

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 ajai b j ≤ b_j\le bj c j ≤ c i c_j \le c_i cjci 的数量。
f ( i ) = d f(i) = d f(i)=d 的数量。 ( 0 ≤ d < n ) (0 \le d < n) (0d<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(l1)

初值也看成修改操作,对整个操作序列进行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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值