首页 > 其他分享 >笔记-CDQ 分治

笔记-CDQ 分治

时间:2024-11-15 10:18:31浏览次数:1  
标签:cnt le int 分治 mid 笔记 CDQ

CDQ 分治

分治,分而治之,一般采取递归的形式,先将要处理的部分分开分别处理,再合并计算。

而 CDQ 分治正是基于分治思想的离线算法。

具体地,CDQ 分治对询问进行分治,对于一个询问区间 \([l,r]\),CDQ 分治进行以下操作:

  1. 处理 \([l,mid]\)。
  2. 处理 \([mid+1,r]\)。
  3. 计算 \([l,mid]\) 中的修改对 \([mid+1,r]\) 的贡献。

CDQ 分治常用于解决点对相关问题。

对于偏序问题,CDQ 分治可以在较优的时间复杂度内求出,如逆序对等二维偏序。归并排序就是 CDQ 分治思想的一种体现。

接下来进入一道例题。

P3810 【模板】三维偏序(陌上花开)

有 \(n\) 个元素,第 \(i\) 个元素有 \(a_i,b_i,c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j \le a_i\) 且 \(b_j \le b_i\) 且 \(c_j \le c_i\) 且 \(j \ne i\) 的 \(j\) 的数量。

对于 \(d \in [0, n)\),求 \(f(i)=d\) 的数量。

\(1 \le n \le 10^5\),\(1 \le a_i,b_i,c_i \le 2 \times 10^5\)。

经典题。

显然这道题是不能 \(O(n^2)\) 暴力枚举的。与点对有关,于是 CDQ 分治。

首先将点按 \(a_i\) 排序,这样就保证只有前面的点对后面的点产生影响。

分治。对于区间 \([l,r]\),假设我们已经处理好了 \([l,mid]\) 和 \([mid+1,r]\),先对这两个区间分别按 \(b_i\) 排序,由于我们在最开始就对整个序列按 \(a_i\) 排序,\([l,mid]\) 中的每个点的 \(a_i\) 一定都是大于 \([mid+1,r]\) 中的每个点的 \(a_i\) 的,所以我们只需要处理 \([l,mid]\) 中 \(b_i\) 和 \(c_i\) 的影响。

将两个区间中的点按 \(b_i\) 从小到大排序(也可用归并实现,文章中使用 sort),然后双指针,\(i\) 代表 \([mid+1,r]\) 从 \(mid+1\) 开始的指针,\(j\) 代表 \([l,mid]\) 从 \(l\) 开始的指针。对于 \(c_i\) 我们可以使用树状数组维护。

对于一个 \(i\),我们将所有 \(b_j \le b_i\) 的点加入树状数组,此时所有加入了树状数组的点 \(x\) 都满足 \(a_x \le a_i,b_x \le b_i\),因此在树状数组上查询 \(c_x \le c_i\) 的点,即 query(c[i]) 即可。

注意清空树状数组。

代码:

#include<bits/stdc++.h> 
using namespace std;
const int N=1e5+5,M=2e5+5;
struct node{
	int a,b,c;
	int ans,cnt;
}a[N],A[N];
int n,m,len;
int ans[N];
namespace BIT{
	int tree[M];
	void add(int x,int k){
		for(;x<=m;x+=x&-x)
			tree[x]+=k;
	}
	int query(int x){
		int res=0;
		for(;x;x-=x&-x)
			res+=tree[x];
		return res;
	}
}
using BIT::add;
using BIT::query;
bool cmpa(node a,node b){
	if(a.a!=b.a)return a.a<b.a;
	if(a.b!=b.b)return a.b<b.b;
	return a.c<b.c;
}
bool cmpb(node a,node b){
	if(a.b!=b.b)return a.b<b.b;
	return a.c<b.c;
}
void cdq(int l,int r){
	if(l>=r)return;
	int mid=l+r>>1;
	cdq(l,mid);
	cdq(mid+1,r);
	sort(a+l,a+mid+1,cmpb);//按 b 排序
	sort(a+mid+1,a+r+1,cmpb);
	int i,j;
	for(i=mid+1,j=l;i<=r;i++){
		while(j<=mid&&a[j].b<=a[i].b){
			add(a[j].c,a[j].cnt);//树状数组维护 c
			j++;
		}
		a[i].ans+=query(a[i].c);
	}
	for(i=l;i<j;i++)
		add(a[i].c,-a[i].cnt);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>A[i].a>>A[i].b>>A[i].c;
	sort(A+1,A+1+n,cmpa);//按 a 排序,保证分开的区间前后 a 相对有序
	int tot=1;
	for(int i=2;i<=n;i++){//将点去重简化计算
		if(A[i].a!=A[i-1].a||A[i].b!=A[i-1].b||A[i].c!=A[i-1].c){
			a[++len]={A[i-1].a,A[i-1].b,A[i-1].c,0,tot};
			tot=1;
		}else{
			tot++;
		}
	}
	a[++len]={A[n].a,A[n].b,A[n].c,0,tot};
	cdq(1,len);
	for(int i=1;i<=len;i++)
		ans[a[i].ans+a[i].cnt-1]+=a[i].cnt;
	for(int i=0;i<n;i++)
		cout<<ans[i]<<'\n';
	return 0;
}

P3374 【模板】树状数组 1

单点加,区间查。

其实 CDQ 分治也能维护这个东西。

把对 \([l,r]\) 的查询转成对 \([1,l-1]\) 和 \([1,r]\) 的查询,对于查询 \([1,x]\),由于只有位置 \(y \le x\) ,且时间早于这个查询的修改能对这个查询造成影响,所以这就是一个二维偏序问题!

二维偏序果断想到 CDQ。我们按时间顺序保存每个操作,对于区间 \([l,mid]\) 和 \([mid+1,r]\),双指针 \(i,j\) 分别表示 \([l,mid]\) 和 \([mid+1,r]\)。由于我们先按时间顺序保存了每个操作,所以 \([l,mid]\) 与 \([mid+1,r]\) 之间的相对时间大小不变。归并操作区间,按操作位置排序,处理 \([l,mid]\) 中的操作时,如果是修改则累加贡献;处理 \([mid+1,r]\) 中的操作时,如果是查询则将贡献保存到答案数组中。然后输出答案数组就好了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
struct node{
    int op,pos,val,id;//op=1修改,=2查询[1,r],=3查询[1,l-1]
    //pos:操作的位置,val:修改的值/第val个答案
    //id:时间顺序
    bool operator <(const node &n)const{
        if(pos!=n.pos)return pos<n.pos;
        return id<n.id;
    }
}q[N*3],tmp[N*3];//tmp:归并数组
int n,m,cnt;
int p,ans[N];
void cdq(int l,int r){
    if(l==r)return;
    int mid=l+r>>1;
    cdq(l,mid);
    cdq(mid+1,r);
    int i,j,k,sum=0;
    for(i=l,j=mid+1,k=l;i<=mid&&j<=r;){//累加贡献
        if(q[i]<q[j]){
            if(q[i].op==1)sum+=q[i].val;
            tmp[k++]=q[i++];
        }else{//将贡献累加到答案数组
            if(q[j].op==2)ans[q[j].val]-=sum;
            if(q[j].op==3)ans[q[j].val]+=sum;
            tmp[k++]=q[j++];
        }
    }
    for(;i<=mid;){
        if(q[i].op==1)sum+=q[i].val;
        tmp[k++]=q[i++];
    }
    for(;j<=r;){
        if(q[j].op==2)ans[q[j].val]-=sum;
        if(q[j].op==3)ans[q[j].val]+=sum;
        tmp[k++]=q[j++];
    }
    for(i=l;i<=r;i++)q[i]=tmp[i];
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cnt++;
        q[cnt].op=1;
        q[cnt].pos=i;
        cin>>q[cnt].val;
    }
    for(int i=1;i<=m;i++){
        int op;
        cin>>op;
        if(op==1){
            cnt++;
            q[cnt].op=1;
            q[cnt].id=i;
            cin>>q[cnt].pos>>q[cnt].val;
        }else{//拆分询问
            p++;
            cnt++;
            q[cnt].op=2;
            q[cnt].val=p;
            q[cnt].id=i;
            cin>>q[cnt].pos;
            q[cnt].pos--;
            cnt++;
            q[cnt].op=3;
            q[cnt].val=p;
            q[cnt].id=i;
            cin>>q[cnt].pos;
        }
    }
    cdq(1,cnt);
    for(int i=1;i<=p;i++)
        cout<<ans[i]<<'\n';
    return 0;
}

标签:cnt,le,int,分治,mid,笔记,CDQ
From: https://www.cnblogs.com/z-Sqrt-xf/p/-/CDQ

相关文章

  • Java8实战笔记(四)
    一、并行数据处理与性能并行流通过对收集源调用parallelStream方法来把集合转换为并行流。并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。//返回从1到给定参数n的所有数字之和//顺序流publicstaticlongsequentialSum(longn){......
  • 论文学习笔记: Generalizable Vision-Tactile Robotic Grasping Strategy forDeformabl
    文章目录目录文章目录一、摘要Abstract二、介绍 Introduction三、相关工作RelatedWork四、方法Methology4.1SensingModalities传感方式4.2TransformerModel 4.3 FactorizationofSpatial-TemporalAttention时空注意力的分解4.4TimeSformer时序变换......
  • 论文学习笔记:Sim-to-Real Grasp Detection with Global-to-Local RGB-D Adaptation
    前言本文重点关注RGB-D抓取检测的模拟到真实问题,并将其表述为域适应问题。一、摘要Abstract目录一、摘要Abstract二、介绍Introduction三、相关工作RelatedWork3.1 Sim-to-RealTransfer模拟到真实的转变3.2Sim-to-realTransferforGraspDetection抓......
  • 《认知觉醒》读书笔记:焦虑与不同层次的成长权重
    最近在阅读《认知觉醒》这本书,读到了其中关于焦虑和耐心的那一章,感觉受到了一些启发,在这里分享给大家。书中对于焦虑的本质的描述非常精辟,这里摘录如下:归结起来,焦虑的原因就两条:想同时做很多事,又想立即看到效果。自己的欲望大于能力,又极度缺乏耐心。焦虑就是因为欲望与能......
  • 软件测试笔记|Python自动化测试|python中的数值运算有何特点?
    一、类型方面特点1.类型丰富:支持整数(int)、浮点数(float)、复数(complex)等多种数值类型。2.动态类型:声明变量时无需指定类型,运行时确定类型。二、精度相关特点1.整数精度:整数类型不会溢出,可处理任意大小整数,受机器内存限制。2.浮点数精度:通常用双精度浮点数表示,符合IEEE7......
  • 软件测试笔记|Python自动化测试|isinstance与type有什么区别,分别有什么特点?
    一、区别isinstance和type都可用于判断对象的类型,但它们有明显区别:1.判断方式•type:直接返回对象的类型,是通过比较对象的类型是否完全相同来判断,更关注对象确切的类型本身。•isinstance:判断一个对象是否是指定类型(或其派生类型)的实例,考虑了继承关系,更灵活些。2.对继......
  • 工作学习笔记(九)负数判断
    今天的工作中,遇到一个问题,是充值金额没有负数校验。以下是几种在Java中添加充值金额为负数判断的常见情况示例,具体取决于应用场景是在网页开发、桌面应用等不同环境下。一、方法参数验证场景(以一个简单的充值方法为例)假设你有一个类,其中有个方法用于处理充值业务,方法接收充值......
  • 浅学AI笔记03:一个Transformer自注意力机制的故事
    ChatGPT、百度文心一言等同类的大模型,都使用了Transformer架构,Transformer最大的特点是其有一个“自注意力机制”,搬个定义说的是:允许模型在处理每个输入元素时,能够考虑其与序列中所有其他元素之间的相关性,从而动态调整其权重。白话来说,就是模型要先理解输入句子的含义,才能......
  • HTTP 协议学习笔记
    HTTP协议学习笔记带新手走进神秘的HTTP协议-超超boy-博客园HTTP首部字段详细介绍-超超boy-博客园《白帽子讲web安全(第二版)》HTTP默认的端口号为80,HTTPS的端口号为443。HTTP是无状态协议,它不对之前发生过的请求和响应的状态进行管理。可以使用Cookie......
  • [豪の学习笔记] 计算机网络#001
    1.1.1-什么是计算机网络计算机网络=通信技术+计算机技术计算机网络就是一种特殊的通信网络定义:计算机网络就是互联的、自治的计算机集合自治:无主从关系互联:互联互通Q:距离远、数量大如何保证互联?通过交换网络互连主机交换节点:路由器或交换机Q:什么是Internet?组成细......