【数据结构】若干线段树写法(实现方法有代码)总结及各种特殊线段树思想

目录

背景

线段树

本人习惯的线段树

主流的线段树

动态开点线段树

可持久化线段树

k小值版经典主席树

区间修改主席树

其他好东西


背景

    明天JSOI2019省选。

    将线段树理了一遍。本文主要包括:本人习惯的线段树写法,主流的线段树写法,类主席树的线段树写法,k小值版经典主席树,区间修改主席树。其中除了第四种,其他都支持区间加与区间求和。

    在文末提到:zkw线段树,动态开点线段树,扫描线,二维线段树,权值线段树(但这些暂时没有代码)。

线段树

本人习惯的线段树

    主要特征是每一个节点记录好左右端点,而不是等遍历时传过来。

    利:写起来方便,而且递归调用时传入参数较少。

    弊:时空常数皆较大

 

struct Segment_tree_type1{//my classical mode, record the left and right point, but may be slower
	struct node{
		int l,r,he,p;
	};
	node t[MAXN*4+1];
	void build(int cn,int cl,int cr)
	{
		t[cn].l = cl;
		t[cn].r = cr;
		t[cn].he = t[cn].p = 0;
		if(cl == cr)return;
		build(cn*2,cl,(cl+cr)/2);
		build(cn*2+1,(cl+cr)/2+1,cr);
	}
	void jia(int cn,int cl,int cr,int cm)
	{
		int l = t[cn].l,r = t[cn].r;
		if(l>cr || r<cl)return;
		if(cl<=l && r<=cr){
			t[cn].he += (r-l+1)*cm;
			t[cn].p += cm;
			return;
		}
		tui(cn);
		jia(cn*2,cl,cr,cm);
		jia(cn*2+1,cl,cr,cm);
		update(cn);
	}
	void tui(int cn)
	{
		t[cn*2].p += t[cn].p;
		t[cn*2].he += t[cn].p * (t[cn*2].r-t[cn*2].l+1);
		t[cn*2+1].p += t[cn].p;
		t[cn*2+1].he += t[cn].p * (t[cn*2+1].r-t[cn*2+1].l+1);
		t[cn].p = 0;
	}
	void update(int cn)
	{
		t[cn].he = t[cn*2].he + t[cn*2+1].he;
	}
	int qiu(int cn,int cl,int cr)
	{
		int l = t[cn].l,r = t[cn].r;
		if(l>cr || r<cl)return 0;
		if(cl<=l && r<=cr)return t[cn].he;
		tui(cn);
		return qiu(cn*2,cl,cr) + qiu(cn*2+1,cl,cr);
	}
};

主流的线段树

    就是可以经常在网上看见的那种,在节点中不记录左右端点。

    利:快一些,一般人都会理解,网上的细节比较丰富

    弊:(我觉得)写起来烦一点,且容易写错

struct Segment_tree_type2{//don't record the left and right point
	struct node{
		int he,p;
	};
	node t[MAXN*4+1];
	void build(int cn,int cl,int cr)
	{
		t[cn].he = t[cn].p = 0;
		if(cl == cr)return;
		build(cn*2,cl,(cl+cr)/2);
		build(cn*2+1,(cl+cr)/2+1,cr);
	}
	void jia(int cn,int cl,int cr,int cm,int l,int r)
	{
		if(cl<=l && r<=cr){
			t[cn].he += cm * (t[cn].r-t[cn].r+1);
			t[cn].p += cm;
			return;
		}
		tui(cn);
		if(cl<=(l+r)/2)jia(cn*2,cl,cr,cm,l,(l+r)/2);
		if(cr>(l+r)/2)jia(cn*2+1,cl,cr,cm,(l+r)/2+1,r);
		update(cn);
	}
	int qiu(int cn,int cl,int cr,int l,int r)
	{
		if(cl<=l && r<=cr)return t[cn].he;
		tui(cn);
		int guo = 0;
		if(cl<=(l+r)/2)guo += qiu(cn*2,cl,cr,l,(l+r)/2);
		if(cr>(l+r)/2)guo += qiu(cn*2+1,cl,cr,(l+r)/2+1,r);
		return guo;
	}
	void tui(int cn,int l,int r)
	{
		int zh = (l+r)/2;
		t[cn*2].p += t[cn].p;
		t[cn*2].he += t[cn].p * (zh-l+1);
		t[cn*2+1].p += t[cn].p;
		t[cn*2+1].he += t[cn].p * (r-zh);
		t[cn].p = 0;
	}
	void update(int cn)
	{
		t[cn].he = t[cn*2].he + t[cn*2+1].he;
	}
};

动态开点线段树

    儿子节点的编号不再是父亲节点乘以二或乘二加一,而是在使用时分配,把左右儿子的编号记录在父亲上。

    利:在有些地方快一点,好写一点,空间更优。有利于主席树的入门理解

    弊:不可zkw化

struct Segment_tree_type3{//just like president tree
	struct node{
		int ls,rs,he,p;
	};
	node t[MAXN*4+1];
	int tlen;
	int build()
	{
		qing(tlen = 1);
	}
	int qing(int cn)
	{
		t[cn].ls = t[cn].rs = t[cn].he = t[cn].p = 0;
		return cn;
	}
	void jia(int cn,int cl,int cr,int cm,int l,int r)
	{
		if(cl<=l && r<=cr){
			t[cn].p += cm;
			t[cn].he += cm * (r-l+1);
			return;
		}
		tui(cn,l,r);
		if(cl <= (l+r)/2)jia(t[cn].ls,cl,cr,cm,l,(l+r)/2);
		if(cr > (l+r)/2)jia(t[cn].rs,cl,cr,cm,(l+r)/2+1,r);
		update(cn);
	}
	int qiu(int cn,int cl,int cr,int l,int r)
	{
		if(cl<=l && r<=cr)return t[cn].he;
		tui(cn,l,r);
		int guo = 0;
		if(cl <= (l+r)/2)guo += qiu(t[cn].ls,cl,cr,l,(l+r)/2);
		if(cr > (l+r)/2)guo += qiu(t[cn].rs,cl,cr,(l+r)/2+1,r);
		return guo;
	}
	void tui_e(int cn,int &cm,int len)
	{
		if(!cm)cm = qing(++tlen);
		t[cm].p += t[cn].p;
		t[cm].he += len * t[cn].p;
	}
	void tui(int cn,int l,int r)
	{
		int zh = (l+r)/2;
		tui_e(cn,t[cn].ls,zh-l+1);
		tui_e(cn,t[cn].rs,r-zh);
		t[cn].p = 0;
	}
	void update(int cn)
	{
		t[cn].he = t[t[cn].ls].he + t[t[cn].rs].he;
	}
};

可持久化线段树

k小值版经典主席树

    功能是单点修改,区间查询(在k小值中这个功能是用来线段树上二分的)。

    基础为类主席树的线段树,只不过在每一回更改时都在原来的基础上新建一个点。新点把原来的点存的值都继承过去后,再进行修改。

    利:适合于主席树入门理解,易写。

    弊(主席树共有):常数太大,空间较大(多一个log{_2}n)。

    弊(这棵树独有):若需要打上懒惰标记,则需要进行更改。

struct Zxs_type1{//the classical president tree, used to change one point
	struct node{
		int ls,rs,he;
	};
	node t[MAXN*40+1];
	int tlen;
	void build()
	{
		qing(tlen = 1);
	}
	int qing(int cn)
	{
		t[cn].ls = t[cn].rs = t[cn].he = 0;
		return cn;
	}
	void jia(int oro,int &ro,int cl,int cm,int l,int r)
	{
		t[ro = ++tlen] = t[oro];
		if(l == r){
			t[cn].he = cm;
			return;
		}
		if(cl <= (l+r)/2)jia(t[cn].ls,t[cn].ls,cl,cm,l,(l+r)/2);
		if(cl > (l+r)/2)jia(t[cn].rs,t[cn].rs,cl,cm,(l+r)/2+1,r);
		update(cn);
	}
	int qiu(int cn,int cl,int cr,int l,int r)
	{
		if(cl<=l && r<=cr)return t[cn].he;
		int guo = 0;
		if(cl <= (l+r)/2)guo += qiu(t[cn].ls,cl,cr,l,(l+r)/2);
		if(cr > (l+r)/2)guo += qiu(t[cn].rs,cl,cr,r,(l+r)/2+1);
		return guo;
	}
	void update(int cn)
	{
		t[cn].he = t[t[cn].ls].he + t[t[cn].rs].he;
	}
};

区间修改主席树

    顾名思义,这棵树比前面那棵高级一点;你可以区间修改,而且与通常版本改动不大,主要就一个新建点的功能。

    利:功能强大

    弊:可能常数太大

struct Zxs_type2{//basic president tree, can change a range
	struct node{
		int ls,rs,he,p;
	};
	node t[MAXN*40+1];
	int tlen;
	void build()
	{
		qing(tlen = 1);
	}
	int qing(int cn)
	{
		t[cn].ls = t[cn].rs = t[cn].he = t[cn].p = 0;
		return cn;
	}
	void jia(int cn,int cl,int cr,int cm,int l,int r)
	{
		if(cl <= l && r<=cr){
			t[cn].p += cm;
			t[cn].he += cm * (r-l+1);
			return;
		}
		tuo(cn);
		tui(cn,l,r);
		if(cl <= (l+r)/2)jia(t[cn].ls,cl,cr,cm,l,(l+r)/2);
		if(cr > (l+r)/2)jia(t[cn].rs,cl,cr,cm,(l+r)/2+1,r);
		update(cn);
	}
	void qiu(int cn,int cl,int cr,int l,int r)
	{
		if(cl<=l && r<=cr)return t[cn].he;
		tui(cn,l,r);
		int guo = 0;
		if(cl <= (l+r)/2)guo += qiu(t[cn].ls,cl,cr,l,(l+r)/2);
		if(cr > (l+r)/2)guo += qiu(t[cn].rs,cl,cr,(l+r)/2+1,r);
		return guo;
	}
	inline void tui(int cn,int l,int r)
	{
		int zh = (l+r)/2;
		tui_e(cn,t[cn].ls,zh-l+1);
		tui_e(cn,t[cn].rs,r-zh);
		t[cn].p = 0;
	}
	inline void tui_e(int cn,int cm,int len)
	{
		t[cm].p += t[cn].p;
		t[cm].he += t[cn].p * len;
	}
	void update(int cn)
	{
		t[cn].he = t[t[cn].ls].he + t[t[cn].rs].he;
	}
	inline void tuo_e(int cn,int &cm)
	{
		t[cm = qing(++tlen)] = t[cn];
	}
	inline void tuo(int cn)
	{
		tuo_e(t[cn].ls,t[cn].ls);
		tuo_e(t[cn].rs,t[cn].rs);
	}
};

其他好东西

    zkw线段树,常数极小,但不是那么使用广泛,容易理解。线段树的修改查询功能都有,也可可持久化。本质是利用左儿子为父亲乘二,右儿子为父亲乘二加一,快速算出目标的位置,再从下往上算。

 

 

    扫描线:应该算是一种应用,可以解决一些二维上看似要用二维线段树或者主席树做的题目。基础功能是算平面上若干矩形的并的面积。把所有矩形抽象成左右两条线,从左往右看,对于当前的x坐标,把左端线所覆盖的纵坐标区间加上1,计算有多少个不为零的纵坐标,再减去右端线覆盖的纵坐标区间(各减一)。

    二维线段树:它死了(基本不会有人用,不会有人出,常数就比log大,基本可以被树套树,主席树,扫描线解决)。

    权值线段树:是对于从零到很大的数(比如1e9)开一棵树,先不全都分配,在用到时再临时分配(当然有些时候先分配也没问题)。(很像动态开点线段树)(k小值的线段树就是一颗权值线段树)(所以可以很方便的拓展到可持久化)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值