ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

cdq分治&整体二分 学习笔记

2022-02-24 22:03:15  阅读:190  来源: 互联网

标签:二分 偏序 int 分治 leq cdq 区间 排序


我只是个萌新,写一篇学习笔记,希望可以帮助未来的自己和他人。如果有大佬看到了错误,您可以在评论区或者私信中指出,并且我非常欢迎您的纠错。


本博客还是从二维偏序开始铺垫,对cdq分治进行讲解(实际上是给自己讲,因为没人看)。

前置知识:归并排序

cdq分治的学习需要保证对归并排序的理解,虽然它是一个基础算法。

二维偏序问题

给定 \(n\) 个元素,第 \(i\) 个元素有两个属性 \(a_i\) 和 \(b_i\) ,设 \(f(i)\) 为满足 \(a_j \leq a_i,b_j \leq b_i,j \neq i\) 的 \(j\) 的个数,对于每一个 \(0 \leq d \leq n - 1\) ,求满足 \(f(i) = d\) 的 \(i\) 的个数。

我们可以发现,对于一个元素 \(i\) ,只有满足 \(a_j \leq a_i\) 的元素才有可能对 \(f(i)\) 产生贡献。

不就是要单个属性小于等于吗,太简单了!我们只需要对属性 \(a\) 单独排序即可!

那么属性 \(b\) 呢?注意到对 \(f(i)\) 有贡献的元素要满足 \(b_j \leq b_i\) ,那么在排序后,我们需要获取在某个元素前面的满足 \(b_j \leq b_i\) 的个数,这一部分显然使用值域 BIT ,按顺序跑元素,然后一个一个加入,然后求前缀和即可。

有一个问题,如果两个元素的 \(a\) 相同,然而 \(b\) 小一点的排到了后面,会发生什么?

这一个的贡献算不到了

所以,在 \(a\) 相同的元素内,我们应该对 \(b\) 排序。

所以 cmp 函数长这个样子:

bool cmp(node x,node y){
	if(x.a != y.a)return x.a < y.a;
  	return x.b < y.b;
}

到目前为止,我相信能来学 cdq 分治的大佬肯定能理解二维偏序,那么就进入我们的正题了:

三维偏序问题(cdq分治模板)

P3810

在二维偏序中,我们在满足 \(a\) 有序的情况下,对 \(b\) 使用值域 BIT 得到了答案。

现在三维偏序多了一个属性,我们无法在一次排序内对两个属性进行排序,怎么办呢...

对新进来的属性进行归并排序

假设我们一开始排序 \(a\) ,对 \(b\) 跑归并,对 \(c\) 用值域 BIT。

在归并排序的过程中,分析左边区间对右边区间的贡献。

为什么可以这样算?

由于归并排序是把原来的数组拆成一半一半的小区间,在拆的过程中我们未曾改变顺序,所以第一次开始算的时候,\(a\) 是有序的!

然后,我们合并区间上去,由于我们之前只是在小区间内合并,对于右边的大区间,我们的 \(a\) 仍然是有序的!

所以,我们可以直接分析左边区间对右边区间的贡献了(右边区间内部自己的贡献,和左边区间内部自己的贡献并不在考虑范围内,这部分会在之前更小的区间的归并排序过程中计算出来)。

这部分长这个样子。

void solve(int l,int r){
	int mid = (l + r) / 2;
	if(l == r)return ;
	solve(l,mid);
	solve(mid + 1,r);
	int posl = l,posr = mid + 1;
	int tot = l;
	while(posl <= mid && posr <= r){
		if(e[posl].b <= e[posr].b){
			t.update(e[posl].c,e[posl].cnt);
			tmp[tot ++] = e[posl ++];
		}
		else {
			e[posr].val += t.query(e[posr].c);
			tmp[tot ++] = e[posr ++];
		}
	}
	while(posl <= mid){
		t.update(e[posl].c,e[posl].cnt);
			tmp[tot ++] = e[posl ++];
	}
	while(posr <= r){
		e[posr].val += t.query(e[posr].c);
		tmp[tot ++] = e[posr ++];
	}
	for(int i = l;i <= mid;i ++)t.update(e[i].c,-e[i].cnt);
	for(int i = l;i <= r;i ++)e[i] = tmp[i];
}

解释一下,这个题有重点,这个 cnt 就是与这个点相同的点的个数。

f 就是这个元素的函数值。

在“并”的过程中,我们保证了 \(b\) 一定是排序好的,既然如此,我们就可以对 \(c\) 用值域 BIT 了。对于左边的区间,进行一个值域树状数组的加,对于右边的区间,直接在值域 BIT 中属性 \(c\) 小于它的个数,值域 BIT 里面只有左区间的元素,所以这就是左区间对右区间的贡献。

这个题就结束了。


由于 cdq分治 只能一次性处理完,所以跑 cdq分治 需要如下条件:

  1. 修改操作对询问的贡献独立,修改操作相互不影响

  2. 题目可以离线处理

cdq分治(在这个题中)实际上帮助我们做到的是通过对时间复杂度增加一个log来降维。

那么,跟上面讲解一样的,在cdq分治中,对于划分出来的两个区间,前一个子问题需要用来解决后一个子问题。


想必到这里,不会有人不懂了吧(

不懂欢迎问我。

贴上完整代码。

#include<bits/stdc++.h>

#define int long long
#define mem(x,y) memset(x,y,sizeof(x))
#define frein freopen("in.in","r",stdin)
#define freout freopen("out.out","w",stdout)
#define debug(x) cout << (#x) << " = " << x << endl;

using namespace std;

int read(){
   int s = 0,w = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();}
   while(ch >= '0' && ch <= '9')s = s * 10 + ch - '0',ch = getchar();
   return s * w;
}

int MAXM;

int lowbit(int x){return x & -x;}

struct BIT{
	int e[200010];
	
	void update(int i,int x){for(;i <= MAXM;i += lowbit(i))e[i] += x;}
	
	int query(int i){
		int s = 0;
		for(;i;i -= lowbit(i))s += e[i];
		return s;
	}
}t;

struct node{
	int a;
	int b;
	int c;
	int cnt;
	int val;
}e[100010],tmp[100010];

int n;
int ans[100010];
int cnt = 1;

bool cmp(node x,node y){
	if(x.a != y.a)return x.a < y.a;
	if(x.b != y.b)return x.b < y.b;
	return x.c < y.c;
}

void solve(int l,int r){
	int mid = (l + r) / 2;
	if(l == r)return ;
	solve(l,mid);
	solve(mid + 1,r);
	int posl = l,posr = mid + 1;
	int tot = l;
	while(posl <= mid && posr <= r){
		if(e[posl].b <= e[posr].b){
			t.update(e[posl].c,e[posl].cnt);
			tmp[tot ++] = e[posl ++];
		}
		else {
			e[posr].val += t.query(e[posr].c);
			tmp[tot ++] = e[posr ++];
		}
	}
	while(posl <= mid){
		t.update(e[posl].c,e[posl].cnt);
			tmp[tot ++] = e[posl ++];
	}
	while(posr <= r){
		e[posr].val += t.query(e[posr].c);
		tmp[tot ++] = e[posr ++];
	}
	for(int i = l;i <= mid;i ++)t.update(e[i].c,-e[i].cnt);
	for(int i = l;i <= r;i ++)e[i] = tmp[i];
}

signed main(){
	cin>>n>>MAXM;
	for(int i = 1;i <= n;i ++){
		e[i].a = read(),e[i].b = read(),e[i].c = read();
		e[i].cnt = 1;
	}
	sort(e + 1,e + n + 1,cmp);
	for(int i = 2;i <= n;i ++){
		if(e[i].a == e[cnt].a && e[i].b == e[cnt].b && e[i].c == e[cnt].c)e[cnt].cnt ++;
		else e[++ cnt] = e[i];
	}
	solve(1,cnt);
	for(int i = 1;i <= cnt;i ++)ans[e[i].val + e[i].cnt - 1] += e[i].cnt;
	for(int i = 0;i <= n - 1;i ++)printf("%lld\n",ans[i]);
	return 0;
}

这个博客还没完,还会有一些简单例题。

关于整体二分:

请叫我填坑。

2022.2.23 13:24


标签:二分,偏序,int,分治,leq,cdq,区间,排序
来源: https://www.cnblogs.com/enderdeer/p/15933715.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有