zoukankan      html  css  js  c++  java
  • 浅谈CDQ分治

    Ⅰ、预备知识

    整体二分???

    Ⅱ、抛出问题

    我们先来看一道洛谷的模板题

    题目背景

    这是一道模板题
    可以使用bitset(不会),CDQ分治,K-DTree(不会)等方式解决。

    题目描述

    (n)个元素,第(i)个元素有(a_i)(b_i)(c_i)三个属性,设(f(i))表示满足(a_jleq a_i)(b_jleq b_i)(c_jleq c_i)(j)的数量。
    对于(din[0, n)),求(f(i)=d)的数量

    输入输出格式

    输入格式:

    第一行两个整数(n、k),分别表示元素数量和最大属性值。
    之后(n)行,每行三个整数(a_i)(b_i)(c_i),分别表示三个属性值。

    输出格式:

    输出(n)行,第(d+1)行表示(f(i)=d)(i)的数量。

    输入输出样例

    输入样例#1:

    10 3
    3 3 3
    2 3 3
    2 3 1
    3 1 1
    3 1 2
    1 3 1
    1 1 2
    1 2 2
    1 3 2
    1 2 1

    输出样例#1:

    3
    1
    3
    0
    1
    0
    1
    0
    0
    1

    说明:

    (1leq Nleq100000,1leq kleq200000)

    Ⅲ、分析问题

    CDQ分治,有国家队某巨佬发明(仿佛是插头dp的论文作者???),主要用于解决带修改,查询,可排序序列的一系列问题,仅可支持离线操作
    CDQ分治的主要步骤有以下几点:
    1、读入(废话)
    1、将已经读入好的数据按照某关键字排序
    2、设当前区间为([l,r]),递归处理左区间([l,mid])和右区间([mid+1,r]),计算左区间的修改操作对右区间的影响(一般用树状数组等数据结构维护)
    3、清除数据结构内的修改数据
    本题又叫三维偏序问题,是CDQ分治的经典题型
    先按照第一维(即(a_i))排序,这样就将问题转化到了二维
    设当前区间为([l,r])
    ([l,mid])([mid+1,r])分别按照第二维排序,此时在左区间中的(a)均小于有区间中的(a)(保证第一维),设左区间已访问到(pl),右区间已访问到(pr)((lleq plleq mid,mid+1leq prleq r))
    (b[pl]<=b[pr])时(保证第二维),即将(pl)点的(c)值加入树状数组
    统计比(pr)点的(c)值小或等于的点的数量(保证第三维)
    详见代码

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 2147483647
    #define mem(i,j) memset(i,j,sizeof(i))
    #define F(i,j,n) for(register int i=j;i<=n;i++)
    #define lowbit(i) i&(-i)//树状数组
    using namespace std;
    struct hahaha{
    	int x,y,z,ans,cnt;//x,y,z分别对应a,b,c;ans表示题目中的f(i),即三维都小于等于i的数量,cnt表示x,y,z相等的点的数量,若只出现一次,则cnt=1
    }f[100010],s[100010];//f为输入数据,s为处理后数据
    int nn,n,m,ans[100010],c[200010];//c是树状数组上的点,ans为最终答案
    inline int read(){
    	int datta=0;char chchc=getchar();bool okoko=0;
    	while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();}
    	while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();}
    	return okoko?-datta:datta;
    }
    inline bool cmpx(hahaha a,hahaha b){//以x为第一关键字排序
    	return a.x==b.x?a.y==b.y?a.z<b.z:a.y<b.y:a.x<b.x;
    }
    inline bool cmpy(hahaha a,hahaha b){//以y为第一关键字排序
    	return a.y==b.y?a.z<b.z:a.y<b.y;
    }
    inline void add(int x,int v){//树状数组修改
    	for(int i=x;i<=m;i+=lowbit(i))
    		c[i]+=v;
    }
    inline int ask(int x){//树状数组查询
    	int res=0;
    	for(int i=x;i;i-=lowbit(i))
    		res+=c[i];
    	return res;
    }
    class CDQ_DC{//之所以用class写是为了装逼
    	private:
    	public:
    	inline void CDQ(int l,int r){
    		if(l==r)//边界条件
    			return ;
    		int mid=(l+r)>>1;
    		CDQ(l,mid);
    		CDQ(mid+1,r);//递归处理左右区间
    		sort(s+l,s+mid+1,cmpy);
    		sort(s+mid+1,s+r+1,cmpy);//按y排序
    		int pl=l,pr=mid+1;
    		while(pr<=r){
    			while(pl<=mid&&s[pl].y<=s[pr].y)
    				add(s[pl].z,s[pl].cnt),pl++;//加点
    			s[pr].ans+=ask(s[pr].z);//处理pr的ans
    			pr++;
    		}
    		F(i,l,pl-1)
    			add(s[i].z,-s[i].cnt);//清空树状数组
    	}
    }C;
    int main(){
    	nn=read();m=read();
    	F(i,1,nn)
    		f[i].x=read(),f[i].y=read(),f[i].z=read();
    	sort(f+1,f+nn+1,cmpx);//按x排序
    	int ct=0;
    	F(i,1,nn){
    		ct++;
    		if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y||f[i].z!=f[i+1].z){
    			s[++n]=f[i];
    			s[n].cnt=ct;//处理数据
    			ct=0;
    		}
    	}
    	C.CDQ(1,n);
    	F(i,1,n)
    		ans[s[i].ans+s[i].cnt-1]+=s[i].cnt;//处理最后答案
    	F(i,0,nn-1)
    		printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    链表相加
    Unity 摄像机跟随
    整数反转
    两数和
    频繁项集挖掘思路
    有关于二进制的乘法计算(原码一位乘)
    JAVA面向对象(下)
    JAVAAPI
    JAVA面向对象()上)
    JAVA基础第一章
  • 原文地址:https://www.cnblogs.com/hzf29721/p/10343179.html
Copyright © 2011-2022 走看看