p3273 棘手的操作(左偏树)

Posted LawrenceSivan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了p3273 棘手的操作(左偏树)相关的知识,希望对你有一定的参考价值。

传送门

久仰大名!

早就听闻这道题正如其名又棘手又恶心,今天总算见识到了。不过确实是左偏树好题

可以先复习一下左偏树

题目大意:有 \\(N\\) 个节点,标号从 \\(1\\)\\(N\\) ,这 \\(N\\) 个节点一开始相互不连通。第 \\(i\\) 个节点的初始权值为 \\(a[i]\\) ,接下来有如下一些操作:

用两个左偏树进行 \\(7\\) 种操作.(第一个用于记录正常操作信息,即 \\(N(Normal)\\) 堆,第二个用于记录最大值信息,即 \\(M(Max)\\) 堆.

关于一些对于左偏树的基本操作:

1.合并:这个就是很基础的左偏树合并

int merge(int x,int y){
	if(!x||!y)return x+y;
	if(val[x]<val[y])swap(x,y);
	push_down(x);
	rs(x)=merge(rs(x),y);
	fa[rs(x)]=x;
	if(dis[ls(x)]<dis[rs(x)])swap(ls(x),rs(x));
	dis[x]=dis[rs(x)]+1;
	return x;
}

2.删除:

inline int delet(int x){
	push_down(x);//首先下放标记
	int p=fa[x],q=merge(ls(x),rs(x));//把x的左右子树合并在一起
	fa[q]=p;//直接把两子树合并完了以后拉在x的父亲上,相当于把x孤立出来
	if(p)lt[p][x==lt[p][1]]=q;//如果p不为空,那么就把两个子树合并完后的q放在x原来的位置上
	while(p){//维护子树的左偏性质
		if(dis[ls(p)]<dis[rs(p)])swap(ls(p),rs(p));
		if(dis[ls(p)]==dis[rs(p)]+1)return root;
		dis[p]=dis[rs(p)]+1;
		q=p;
		p=fa[p];
	}
	return q;//最后返回根节点,即堆顶
}

3.单点增加:

inline int add_node(int x,int v){
	int findx=find(x);
	if(findx==x){//如果他就是根节点
		if(ls(x)+rs(x)==0){//如果没有左右儿子,即它是孤立节点
			val[x]+=v;//直接加
			return x;//返回根节点
		}
		else{//否则
			if(ls(x))findx=ls(x);
			else findx=rs(x);
		}
	}
	delet(x);//删掉x
	val[x]+=v+sum(x);//加上
	clear(x);//把该位置清空
	return merge(find(findx),x);//把x重新放进去
}

4.\\(push\\_down\\)

inline void push_down(int x){
	if(ls(x))val[ls(x)]+=tag[x],tag[ls(x)]+=tag[x];
	if(rs(x))val[rs(x)]+=tag[x],tag[rs(x)]+=tag[x];
	tag[x]=0;
}
  • \\(U\\) \\(x\\) \\(y\\): 加一条边,连接第 \\(x\\) 个节点和第 \\(y\\) 个节点.

我们需要做的就是直接在 \\(N\\) 堆中 \\(merge\\) 就可以了。

合并的两个堆顶中较小的一个可以直接从第二个左偏树中删除。

if(op[0]==\'U\'){
	x=read();y=read();
	findx=N.find(x),findy=N.find(y);
	if(findx!=findy){
		tmp=N.merge(findx,findy);//合并
		if(tmp==findx)root=M.delet(findy);//删除较小的
		else root=M.delet(findx);
	}
}
  • \\(A1\\) \\(x\\) \\(v\\): 将第 \\(x\\) 个节点的权值增加 \\(v\\).

把这个点从两个堆中删除,加上以后再放回去。

else if(op[0]==\'A\'){
	if(op[1]==\'1\'){
		x=read();y=read();
		findx=N.find(x);
		root=M.delet(findx);//在M中删除
		int z=N.add_node(x,y);//在N中删除并修改
		M.val[z]=N.val[z];
		M.clear(z);
		root=M.merge(root,z);
	}
}
  • \\(A2\\) \\(x\\) \\(v\\): 将第 \\(x\\) 个节点所在的连通块的所有节点的权值都增加 \\(v\\).

对于这个连通块我们打上一个标记,并且只对堆顶进行修改,等到后面需要 \\(merge\\) 或者删除节点的时候我们再把 \\(tag\\) 标记下传。

else if(op[0]==\'A\'){
	else if(op[1]==\'2\'){
	x=read();y=read();
		findx=N.find(x);
		root=M.delet(findx);//因为要更改值了,所以我们把它从堆M中删除
		N.val[findx]+=y;//改堆顶
		N.tag[findx]+=y;//打标记
		M.val[findx]=N.val[findx];
		M.clear(findx);//把原来位置清掉
		root=M.merge(root,findx);//把新值扔进M堆
	}
}
  • \\(A3\\) \\(v\\): 将所有节点的权值都增加 \\(v\\).

我们记录一个 \\(all\\_tag\\) ,表示对所有的数字进行的操作,最后输出的时候我们加上就可以了。

else if(op[0]==\'A\'){
	else if(op[1]==\'3\'){
		x=read();
		all_tag+=x;
	}
}
  • \\(F1\\) \\(x\\): 输出第 \\(x\\) 个节点当前的权值.

我们直接输出第一个堆里面的该节点的值,不要忘记 \\(all\\_tag\\)

else if(op[0]==\'F\'){
	if(op[1]==\'1\'){
		x=read();
		printf("%d\\n",N.val[x]+all_tag+N.sum(x));
	}
}
  • \\(F2\\) \\(x\\): 输出第 \\(x\\) 个节点所在的连通块中,权值最大的节点的权值.

直接输出第一个堆中该节点的堆顶。

else if(op[0]==\'F\'){
	else if(op[1]==\'2\'){
		x=read();
		printf("%d\\n",N.val[N.find(x)]+all_tag);
	}
}

  • \\(F3\\): 输出所有节点中,权值最大的节点的权值.

直接输出第二个堆的堆顶。

else if(op[0]==\'F\'){
	else if(op[1]==\'3\'){
		printf("%d\\n",M.val[root]+all_tag);
	}
}

然后就是一个码农题?

//#define LawrenceSivan

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define re register
const int maxn=3e5+5;
#define INF 0x3f3f3f3f

inline int mymax(int a,int b){
	return a>b?a:b;
} 

int n,q,a[maxn];
int root,all_tag;
char op[2];
int x,y;

struct lefttree{
	int lt[maxn][2],val[maxn],tag[maxn],fa[maxn],dis[maxn];
	#define ls(x) lt[x][0]
	#define rs(x) lt[x][1]
	
	inline void clear(int x){
		ls(x)=rs(x)=fa[x]=0;		
	}
	
	inline int sum(int x){
		int res=0;
		while(x=fa[x])res+=tag[x];
		return res;
	}
	
	inline int find(int x){
		while(fa[x])x=fa[x];
		return x;
	}
	
	inline void push_down(int x){
		if(ls(x))val[ls(x)]+=tag[x],tag[ls(x)]+=tag[x];
		if(rs(x))val[rs(x)]+=tag[x],tag[rs(x)]+=tag[x];
		tag[x]=0;
	}
	
	int merge(int x,int y){
		if(!x||!y)return x+y;
		if(val[x]<val[y])swap(x,y);
		push_down(x);
		rs(x)=merge(rs(x),y);
		fa[rs(x)]=x;
		if(dis[ls(x)]<dis[rs(x)])swap(ls(x),rs(x));
		dis[x]=dis[rs(x)]+1;
		return x;
	}
	
	inline int delet(int x){
		push_down(x);
		int p=fa[x],q=merge(ls(x),rs(x));
		fa[q]=p;
		if(p)lt[p][x==lt[p][1]]=q;
		while(p){
			if(dis[ls(p)]<dis[rs(p)])swap(ls(p),rs(p));
			if(dis[ls(p)]==dis[rs(p)]+1)return root;
			dis[p]=dis[rs(p)]+1;
			q=p;
			p=fa[p];
		}
		return q;
	}
	
	inline void add_tree(int x,int v){
		int findx=find(x);
		val[findx]+=v;
		tag[findx]+=v;
	}
	
	inline int add_node(int x,int v){
		int findx=find(x);
		if(findx==x){
			if(ls(x)+rs(x)==0){
				val[x]+=v;
				return x;
			}
			else{
				if(ls(x))findx=ls(x);
				else findx=rs(x);
			}
		}
		delet(x);
		val[x]+=v+sum(x);
		clear(x);
		return merge(find(findx),x);
	}
	
	int build(){
		queue <int> q;
		for(re int i=1;i<=n;i++){
			q.push(i);
		}
		int x,y,z;
		while(q.size()>1){
			x=q.front();q.pop();
			y=q.front();q.pop();
			z=merge(x,y);
			q.push(z); 
		}
		return q.front();
	}
	
} N,M;

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch==\'-\')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}

inline void U(){
	x=read();y=read();
	int findx=N.find(x),findy=N.find(y);
	if(findx!=findy){
		int tmp=N.merge(findx,findy);
		if(tmp==findx)root=M.delet(findy);
		else root=M.delet(findx);
	}
}

inline void A1(){
	x=read();y=read();
	int findx=N.find(x);
	root=M.delet(findx);
	int z=N.add_node(x,y);
	M.val[z]=N.val[z];
	M.clear(z);
	root=M.merge(root,z);
}
inline void A2(){
	x=read();y=read();
	int findx=N.find(x);
	root=M.delet(findx);
	N.add_tree(x,y);
	M.val[findx]=N.val[findx];
	M.clear(findx);
	root=M.merge(root,findx);
}

inline void A3(){
	x=read();
	all_tag+=x;
}

inline void F1(){
	x=read();
	printf("%d\\n",N.val[x]+all_tag+N.sum(x));
}

inline void F2(){
	x=read();
	printf("%d\\n",N.val[N.find(x)]+all_tag);
}

inline void F3(){
	printf("%d\\n",M.val[root]+all_tag);
}

int main(){
#ifdef LawrenceSivan
    freopen("aa.in","r",stdin);
    freopen("aa.out","w",stdout);
#endif
	n=read();
	for(re int i=1;i<=n;i++){
		a[i]=read();
		N.val[i]=M.val[i]=a[i];
	}
	
	root=M.build();
	q=read();
	int findx,findy,tmp;
	
	for(re int i=1;i<=q;i++){
		scanf("%s",op);
		if(op[0]==\'U\')U();
		else if(op[0]==\'A\'){
			if(op[1]==\'1\')A1();
			else if(op[1]==\'2\')A2();
			else if(op[1]==\'3\')A3();
		}
		else if(op[0]==\'F\'){
			if(op[1]==\'1\')F1();
			else if(op[1]==\'2\')F2();
			else if(op[1]==\'3\')F3();
		}
	}
	
	
	return 0;
}

以上是关于p3273 棘手的操作(左偏树)的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ2333棘手的操作(左偏树,STL)

bzoj千题计划218:bzoj2333: [SCOI2011]棘手的操作

[BZOJ2333][SCOI2011]棘手的操作

bzoj2333[SCOI2011]棘手的操作 洛谷P3273 [SCOI2011]棘手的操作

Chapter 5. 数据结构 左偏树

左偏树(可并堆)