p3273 棘手的操作(左偏树)

2021/5/14 18:25:21

本文主要是介绍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 棘手的操作(左偏树)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程