目录
背景
明天是JSOI2019省选R1D1。今天考虑到近期数据结构手生了,决定把几种线段树理一下。
在这个过程中,有两点发现。其一是在某些实现下,可持久化的数据结构往往只需要普通的数据结构加入几行代码即可。这就意味着,可持久化的本质就应当是由基础数据结构适当记录更改而实现的。
当然,像这样的普通数据结构,它越接近于算法的本质(比如树形用指针,而不是用数组,甚至是用一些粗略的计算来确定位置),就越容易进行可持久化的更改。
这或许也暗示着我们,发展的前提,是抓住本质。
线段树
这是一棵可爱的,能支持区间加和区间求和的线段树。
区间修改以及区间求最值,并没有多大本质区别,故未写。
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;
}
};
主席树
而这是一棵同样可爱的,同样支持区间加和区间求和的主席树。
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);
}
};
可以看到,我的这一棵主席树,比前面的线段树多的只是三处:
1、t数组大小由4倍改到40倍
2、在jia(区间加)函数中,下传标记(tui)前先创建新点(tuo)
3、在最末尾的地方多了两个用来创建新点的函数(tuo和tuo_e)
结语
个人认为这样子来学习可持久化算法,可以很好地在考场上临时添加可持久化功能,也能较为深刻地理解可持久化究竟干了什么。最关键的是,它会使代码更便于调试与增加新功能。