动态树:一种用于维护有根树森林,支持对树的分割和合并操作的数据结构。
通常支持如下操作:
查询一个点的父亲
查询一个点所在的树的根
修改某个节点的权
向从某个节点到它所在的树的根的路径上的所有的节点的权增加一个数
查询从某个节点到它所在的树的根的路径上的所有的节点的权的最小值
把一棵树从某个节点和它的父亲处断开,使其成为两棵树
让一棵树的根成为另一棵树的某个节点的儿子,从而合并这两棵树
把某棵树的根修改为它的某个节点
查询在同一棵树上的两个节点的LCA
修改以某个节点为根的子树的所有节点的权
查询以某个节点为根的子树的所有节点的权的最小值
染色[SDOI2011]
题目描述:
给定一棵有n个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”、“222”和“1”。
请你写一个程序依次完成这m个操作。
输入格式:
第一行包含2个整数n和m,分别表示节点数和操作数;
第二行包含n个正整数表示n个节点的初始颜色
下面行每行包含两个整数x和y,表示x和y之间有一条无向边。
下面行每行描述一个操作:
“C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;
“Q a b”表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。
通常支持如下操作:
查询一个点的父亲
查询一个点所在的树的根
修改某个节点的权
向从某个节点到它所在的树的根的路径上的所有的节点的权增加一个数
查询从某个节点到它所在的树的根的路径上的所有的节点的权的最小值
把一棵树从某个节点和它的父亲处断开,使其成为两棵树
让一棵树的根成为另一棵树的某个节点的儿子,从而合并这两棵树
把某棵树的根修改为它的某个节点
查询在同一棵树上的两个节点的LCA
修改以某个节点为根的子树的所有节点的权
查询以某个节点为根的子树的所有节点的权的最小值
struct node
{
node *p,*ch[2]; //父亲和两个孩子
int mx,rev,val,add; //mx为该节点与其两个孩子节点的较大值,val为其自身值
}nodes[N],*cur,*null;
int n,m,u,v,w;
node *newnode(int key) //创建值为key的节点
{
cur->p=cur->ch[0]=cur->ch[1]=null;
cur->mx=cur->val=key;
cur->rev=0;
return cur++;
}
void init() // 初始化一个单节点的树
{
null=nodes;
null->p=null->ch[0]=null->ch[1]=null;
null->mx=null->val=-INF;
null->add=0;
null->rev=0;
cur=nodes+1;
}
struct dynamictree //动态树结构
{
bool isroot(node *x)//判根
{
return x==null || x->p->ch[0]!=x && x->p->ch[1]!=x;
}
void pushup(node *x)
{
x->mx=max(x->val,max(x->ch[0]->mx,x->ch[1]->mx));
}
void pushdown(node *x)
{
if(x==null) return;
if(x->rev)
{
x->rev=0;
if(x->ch[0]!=null) x->ch[0]->rev^=1;
if(x->ch[1]!=null) x->ch[1]->rev^=1;
swap(x->ch[0],x->ch[1]);
}
if(x->add)
{
if(x->ch[0]!=null) x->ch[0]->add+=x->add,x->ch[0]->val+=x->add,x->ch[0]->mx+=x->add;
if(x->ch[1]!=null) x->ch[1]->add+=x->add,x->ch[1]->val+=x->add,x->ch[1]->mx+=x->add;
x->add=0;
}
}
void rotate(node *x,int f) //交换节点x与其父亲节点y,f为0,y变为x的左孩子,f为1,y变为x的的右孩子
{
if(isroot(x)) return;
node *y=x->p;
y->ch[!f]=x->ch[f];
x->p=y->p;
if(x->ch[f]!=null) x->ch[f]->p=y;
if(y!=null)
{
if(y==y->p->ch[1]) y->p->ch[1]=x;
else if(y==y->p->ch[0]) y->p->ch[0]=x;
}
x->ch[f]=y;
y->p=x;
pushup(y);
}
//假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的
//那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后
//进行重构,把被查找的条目搬移到离树根近一些的地方。splay应运而生。伸展树是一种自调整对树
//形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
//简而言之,这个操作就是用来提高查找效率的。
void splay(node *x)
{
static node *sta[N];
int top=1;
sta[0]=x;
for(node *y=x;!isroot(y);y=y->p)
sta[top++]=y->p;
while (top) pushdown(sta[--top]);
while (!isroot(x))
{
node *y=x->p;
if(isroot(y)) rotate(x,x==y->ch[0]);
else
{
int f=y->p->ch[0]==y;
if(y->ch[f]==x) rotate(x,!f);
else rotate(y,f);
rotate(x,f);
}
}
pushup(x);
}//ACCESS 操作是动态树 的所有操作的基础. 假设调用了过程ACCESS(v), 那么从点v 到根结点的路径就成为一条新的链
node *access(node *u)
{
node *v=null;
while (u!=null)
{
splay(u);
v->p=u;
u->ch[1]=v;
pushup(u);
v=u;
u=u->p;
}
return v;
}
node *link(node *u,node *v)//合并两棵树
{
access(u);
splay(u);
u->rev=1;
u->p=v;
}
node *cut(node *u)//分离这个节点的左子树
{
access(u);
splay(u);
u->ch[0]=u->ch[0]->p=null;
pushup(u);
}
void changeroot(node *u)//换根
{
access(u)->rev^=1;
}
node *getroot(node *u)//找节点u的根
{
access(u);
splay(u);
while (u->p!=null) u=u->p;
splay(u);
return u;
}
bool queryuv(node *u,node *v)//判断是否在同一子树
{
while (u->p!=null) u=u->p;
while (v->p!=null) v=v->p;
return u==v;
}
}splay;
染色[SDOI2011]
题目描述:
给定一棵有n个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”、“222”和“1”。
请你写一个程序依次完成这m个操作。
输入格式:
第一行包含2个整数n和m,分别表示节点数和操作数;
第二行包含n个正整数表示n个节点的初始颜色
下面行每行包含两个整数x和y,表示x和y之间有一条无向边。
下面行每行描述一个操作:
“C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;
“Q a b”表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。