zoukankan      html  css  js  c++  java
  • 划分树

    一、相关定义

    【主要特征】

    • 主要用于求解给定区间的第k大的元素
    • 时间复杂度为O(logn);
    • 快排也可以快速找出,但快排会改变原序列,每求一次都得恢复序列。

    【划分树】

    预处理:

    • ①int a[maxn];                  //存储题目给定的原序列 
    • ②int sorted[maxn];   //将原序列按由小至大的顺序排序得到新序列,并将新序列存入sorted数组

    建立步骤:

    • 将由n 个数组成的序列不断划分;
    • 根结点就是原序列
    • ①父结点所有元素排序后的前一半(即l~mid)保持其在原序列中的相对顺序存入左孩子;
    • ②父结点所有元素排序后的后一半(即mid+1~r)保持其在原序列中的相对顺序存入右孩子;
    • 对每个子结点,也分别执行上述①②的操作,直到结点中只有一个元素为止。

    见下图:  


    红点标记的是将从本节点进入左孩子的元素。

    关键点:

    需要一个辅助数组“int num[maxn];”;

    num[i]表示第i个数之前(包括i)有多少个数进入左孩子。

    二、算法模板

    【存储结构】

    //采用层次存储结构(由下而上,由左到右,每层两个孩子)
    const int N=1e5+5;
    int sorted[N];              //对原来集合中的元素排序后的值
    struct node
    {
             int valu[N];       //val记录第k层当前位置元素的值
             int num[N];        //num记录元素所在区间的当前位置及之前进入左孩子的个数
             LL sum[N];         //sum记录比当前元素小的元素的和
    }t[20];            //设划分树有20层

    【建树】

    过程:

    1.  找到序列的中位数,将大于中位数的扔到中位数的左边,小于中位数的扔到数的右边;(要按在原序列的相对位置扔)
    2. 对每个子区间,也分别执行第一步操作,直到序列中只有一个元素为止。

    可以看出,建树是一个递归的过程,与线段树的建树有相似之处。

    注意:

    1.  建树划分的标准是中位数,所以需要排序;(只需排一次序,想想why,想不通点击
    2. 划分的过程,需要记录第i层[1,j]之间有多个数据被分到了左边(注意这里用的是闭区间)。

    模板说明:

    • 划分树的建立和普通的二叉树的建立过程差不多,仍然采取先序的过程(先根节点,然后左右孩子)
    • 树的建立相对比较简单,我们依据的是已经排好序的位置进行建树,所以先用快排将原集合还序
    • 要维护每个节点的num域

    模板版本(一)

    在版本一里,每个区间的起点的num[ind][lft]和sum[ind][lft]都会被赋值为0

    void build(int lft,int rht,int ind)
    {
    	if(lft==rht) return;
    	int mid=lft+(rht-lft)>>1;
    	int isame=mid-lft+1,same=0;
    	/* isame用来标记和中间值val_mid相等的且分到左孩子的数的个数; 
    	   初始时,假定当前区间[lft,rht]有mid-lft+1个和valu_mid相等。
    	   先踢掉比中间值小的,剩下的就是要插入到左边的
    	 */
    	for(int i=lft;i<=rht;i++)
    		if(t[ind].valu[i]<sorted[mid]) isame--;
    	int ln=lft,rn=mid+1;
    	for(int i=lft;i<=rht;i++)
    	{
    		if(i==lft)		//初始一个子树
    		{
    			t[p].num[i]=0;
    			t[p].sum[i]=0;
    		}
    		else 			//初始区间下一个节点
    		{
    			t[p].num[i]=t[p].num[i-1];
    			t[p].sum[i]=t[p].sum[i-1];
    		}
    		/* 如果大于,肯定进入右孩子;否则判断是否还有相等的应该进入左孩子的,
    		   没有,直接进入右孩子,否则进入左孩子,同时更新节点的sum域和num域
    		 */
    		if(t[p].val[i]<sorted[mid])
    		{
    			t[p].num[i]++;
    			t[p].sum[i]+=t[p].valu[i];
    			t[p+1].valu[ln++]=t[p].valu[i];
    		}
    		else if(t[p].valu[i]>sorted[mid])
    			t[p+1].valu[rn++]=t[p].valu[i];
    		else 
    		{
    			if(same<isame)
    			{
    				same++;
    				t[p].num[i]++;
    				t[p].sum[i]+=t[p].valu[i];
    				t[p+1].valu[ln++]=t[p].valu[i];
    			}
    			else 
    			{
    				t[p+1].valu[rn++]=t[p].valu[i];
    			}
    		}
    	}
    	build(lft,mid,ind+1);
    	build(mid+1,rht,ind+1);
    }
    

    模板版本(二)

    在版本一里,每个区间的起点的num[ind][lft]和sum[ind][lft]都会被赋值为0。而这个版本中并没有这么做,而是在前面的基础上继续。也就是说只有将num[ind][0]和sum[ind][0]赋值为0。另外这个版本里我将排序后的数组是order而不是sorted。

    void build(int lft,int rht,int ind)
    {
    	if(lft==rht) return;
    	int mid=MID(lft,rht);
    	int same=mid-lft+1,ln=lft,rn=mid+1;
    	for(int i=lft;i<=rht;i++)
    		if(valu[ind][i]<order[mid]) same--;
    	for(int i=lft;i<=rht;i++)
    	{
    		int flag=0;
    		if((valu[ind][i]<order[mid])||valu[ind][i]==order[mid]&&same>0)
    		{
    			flag=1;
    			valu[ind+1][ln++]=valu[ind][i];
    			if(valu[ind][i]==order[mid]) same--;
    			lsum[ind][i]=lsum[ind][i-1]+valu[ind][i];
    		}
    		else
    		{
    			lsum[ind][i]=lsum[ind][i-1];
    			valu[ind+1][rn++]=valu[ind][i];
    		}
    		toLft[ind][i]=toLft[ind][i-1]+flag;
    	}
    	build(lft,mid,ind+1);
    	build(mid+1,rht,ind+1);
    }
    

    【查找】

    在区间[a,b]上查找第k大的元素,同时返回它的位置和区间小于[a,b]的所有数的和。

    1. 如果t[p].num[b]-t[p].num[a-1]>=k,即,进入p的左孩子的个数已经超过k个,那么就往左孩子里面查找,同时更新[a,b]=>[lft+t[p].num[a-1],lft+t[p].num[b]-1]
    2. 如果t[p].num[b]-t[p].num[a-1]<k,即,进入p的左孩子的个数小于k个,那么就要往右孩子查找第k-s(s表示进入左孩子的个数)个元素。同时更新sum域,因而这样求出的sum只是严格小于在[a,b]区间中第k大的数的和。

    模板版本(一)

    /*在区间[a,b]上查找第k大元素,同时sum返回区间[a,b]中小于第k大元素的和*/
    int query(int a,int b,int k,int p,int lft,int rht)
    {
    	if(lft==rht) return t[p].valu[a];
    	/*到达叶子结点就找到该元素,返回
    	S     记录区间[a,b]中进入左孩子的元素的个数
    	SS   记录区间[lft,a-1]中进入左孩子的元素的个数
    	SSS 记录区间[a,b]中小于第k大的元素的值和
    	B2   表示[lft,a-1]中分到右孩子的个数
    	BB   表示[a,b]中分到右孩子的个数
            */
    	int s,ss,b2,bb,mid=lft+(rht-lft)/2;
    	double sss=0;
    	if(a==lft)//端点重合的情况,单独考虑
    	{
    		s = t[p].num[b]; 
    		ss = 0; 
    		sss = t[p].sum[b];
    	}
    	else 
    	{
    		s = t[p].num[b] - t[p].num[a-1];
    		ss = t[p].num[a-1]; 
    		sss = t[p].sum[b] - t[p].sum[a-1];
    	}
    	if(s>=k)	//进入左孩子,同时更新区间端点值。
    	{
    		a = lft + ss;// 
    		b = lft + ss + s - 1; 
    		return query(a, b, k, p+1, lft, mid);
    	}
    	else 
    	{
    		bb = a - lft - ss;
    		b2 = b - a - 1 - s; 
    		a = mid + bb + 1; 
    		b = mid + bb + b2; 
    		sum += sss;
    		return query(a,b,k-s,p+1,mid+1,rht);
    	}
    }
    

    模板版本(二) 

    /*在区间[a,b]上查找第k大元素,同时sum返回区间[a,b]中小于第k大元素的和*/
    int query(int st,int ed,int k,int lft,int rht,int ind)
    {
    	if(lft==rht)   return valu[ind][lft];
    	/*
    	  	lx表示从lft到st-1这段区间内有多少个数进入左子树
    		ly表示从st到ed这段区间内有多少个数进入左子树
    		rx表示从lft到st-1这段区间内有多少个数进入右子树
    		ry表示从st到ed这段区间内有多少个数进入右子树
    	 */
    	int mid=MID(lft,rht);
    	int lx=toLft[ind][st-1]-toLft[ind][lft-1];
    	int ly=toLft[ind][ed]-toLft[ind][st-1];
    	int rx=st-1-lft+1-lx;
    	int ry=ed-st+1-ly;
    	if(ly>=k) return query(lft+lx,lft+lx+ly-1,k,lft,mid,ind+1);
    	else
    	{
    		isum+=lsum[ind][ed]-lsum[ind][st-1];
    		st=mid+1+rx;
    		ed=mid+1+rx+ry-1;
    		return query(st,ed,k-ly,mid+1,rht,ind+1);
    	}
    }
    

    三、结合题目

    题目:POJ 2104

    题目意思:给你n 个数的原序列,有m次询问,每次询问给出l、r、k,求原序列l到r之间第k大的数。n范围10万,m范围5千。

    提示:这道题用快排也可以过,快排过的时间复杂度n*m,而划分树是m*logn(实际上应该是nlogn才对,因为建图时间是nlogn,n又比m大),分别AC后,时间相差很明显。

    题解:点击

    风格一:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define MID(a,b) (a+((b-a)>>1))
    const int N=1e5+5;
    struct node
    {
    	int valu[N],num[N];
    };
    struct P_Tree
    {
    	int n,order[N];
    	node t[20];
    	void init(int len)
    	{
    		n=len;
    		for(int i=1;i<=n;i++)
    		{
    			scanf("%d",&order[i]);
    			t[0].valu[i]=order[i];
    		}
    		sort(order+1,order+1+n);
    		build(1,n,0);
    	}
    	void build(int lft,int rht,int ind)
    	{
    	    //cout<<lft<<" "<<rht<<endl;
    	    if(lft==rht) return;
    		int mid=MID(lft,rht);
    		int lsame=mid-lft+1,same=0,ln=lft,rn=mid+1;
    		for(int i=lft;i<=rht;i++)
    			if(t[ind].valu[i]<order[mid]) lsame--;
    		for(int i=lft;i<=rht;i++)
    		{
    			if(i==lft) t[ind].num[i]=0;
    			else t[ind].num[i]+=t[ind].num[i-1];
    
    			if(t[ind].valu[i]<order[mid])
    				t[ind].num[i]++,t[ind+1].valu[ln++]=t[ind].valu[i];
    			else if(t[ind].valu[i]>order[mid])
    				t[ind+1].valu[rn++]=t[ind].valu[i];
    			else
    			{
    				same++;
    				if(lsame>=same)
    					t[ind].num[i]++,t[ind+1].valu[ln++]=t[ind].valu[i];
    				else t[ind+1].valu[rn++]=t[ind].valu[i];
    			}
    		}
    		build(lft,mid,ind+1);
    		build(mid+1,rht,ind+1);
    	}
    	int query(int st,int ed,int k,int lft,int rht,int ind)
    	{
    		if(lft==rht) return t[ind].valu[lft];
    		int lx,ly,rx,ry,mid=MID(lft,rht);
    		if(st==lft) lx=0,ly=t[ind].num[ed];
    		else lx=t[ind].num[st-1],ly=t[ind].num[ed]-t[ind].num[st-1];
    		if(ly>=k)
    		{
    			st=lft+lx;
    			ed=lft+lx+ly-1;
    			return query(st,ed,k,lft,mid,ind+1);
    		}
    		else
    		{
    			rx=st-1-lft+1-lx;
    			ry=ed-st+1-ly;
    			st=mid+1+rx;
    			ed=mid+1+rx+ry-1;
    			return query(st,ed,k-ly,mid+1,rht,ind+1);
    		}
    	}
    }tree;
    int main()
    {
    	int n,m;
    	while(scanf("%d%d",&n,&m)!=EOF)
    	{
    		tree.init(n);
    		for(int i=0;i<m;i++)
    		{
    			int a,b,c;
    			scanf("%d%d%d",&a,&b,&c);
    			int res=tree.query(a,b,c,1,n,0);
    			printf("%d
    ",res);
    		}
    	}
    	return 0;
    }

    风格二:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define MID(a,b) (a+((b-a)>>1))
    typedef long long LL;
    const int N=1e5+5;
    struct P_Tree
    {
    	int n,order[N];
    	int valu[20][N],num[20][N];
    	LL sum[N],lsum[20][N],isum;
    	void init(int len)
    	{
    		n=len;	sum[0]=0;
    		for(int i=0;i<20;i++) valu[i][0]=0,num[i][0]=0,lsum[i][0]=0;
    		for(int i=1;i<=n;i++)
    		{
    			scanf("%d",&order[i]);
    			valu[0][i]=order[i];
    			sum[i]=sum[i-1]+order[i];
    		}
    		sort(order+1,order+1+n);
    		build(1,n,0);
    	}
    	void build(int lft,int rht,int ind)
    	{
    		if(lft==rht) return;
    
    		int mid=MID(lft,rht);
    		int same=mid-lft+1,ln=lft,rn=mid+1;
    		for(int i=lft;i<=rht;i++)
    			if(valu[ind][i]<order[mid]) same--;
    		for(int i=lft;i<=rht;i++)
    		{
    			int flag=0;
    			if((valu[ind][i]<order[mid])||(valu[ind][i]==order[mid]&&same))
    			{
    				flag=1;
    				valu[ind+1][ln++]=valu[ind][i];
    				lsum[ind][i]=lsum[ind][i-1]+valu[ind][i];
    				if(valu[ind][i]==order[mid]) same--;
    			}
    			else 
    			{
    				valu[ind+1][rn++]=valu[ind][i];
    				lsum[ind][i]=lsum[ind][i-1];
    			}
    			num[ind][i]=num[ind][i-1]+flag;
    		}
    		build(lft,mid,ind+1);
    		build(mid+1,rht,ind+1);
    	}
    	int query(int st,int ed,int k,int lft,int rht,int ind)
    	{
    		if(lft==rht) return valu[ind][lft];
    
    		int mid=MID(lft,rht);
    		int lx=num[ind][st-1]-num[ind][lft-1];
    		int ly=num[ind][ed]-num[ind][st-1];
    		int rx=st-1-lft+1-lx;
    		int ry=ed-st+1-ly;
    		if(ly>=k) return query(lft+lx,lft+lx+ly-1,k,lft,mid,ind+1);
    		else 
    		{
    			isum+=lsum[ind][ed]-lsum[ind][st-1];
    			st=mid+1+rx;
    			ed=mid+1+rx+ry-1;
    			return query(st,ed,k-ly,mid+1,rht,ind+1);
    		}
    	}
    }tree;
    int main()
    {
    	int n,m;
    	while(scanf("%d%d",&n,&m)!=EOF)
    	{
    		tree.init(n);
    		for(int i=0;i<m;i++)
    		{
    			int a,b,c;
    			scanf("%d%d%d",&a,&b,&c);
    			int res=tree.query(a,b,c,1,n,0);
    			printf("%d
    ",res);
    		}
    	}
    	return 0;
    }
  • 相关阅读:
    03-数据结构(C语言版)
    01C语言基础(二)
    python杂项
    mmdetection源码阅读
    建立文件软连接
    python将test01文件夹中的文件剪切到test02文件夹中
    Probabilistic two-stage detection
    Activate-or-Not:learning-customized-activation
    CenterNet和CenterNet2笔记
    OpenCV视频检测人脸
  • 原文地址:https://www.cnblogs.com/xzxl/p/7215774.html
Copyright © 2011-2022 走看看