传送门
久仰大名!
早就听闻这道题正如其名又棘手又恶心,今天总算见识到了。不过确实是左偏树好题
可以先复习一下左偏树
题目大意:有 \(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; }
我们需要做的就是直接在 \(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); } }
把这个点从两个堆中删除,加上以后再放回去。
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); } }
对于这个连通块我们打上一个标记,并且只对堆顶进行修改,等到后面需要 \(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堆 } }
我们记录一个 \(all\_tag\) ,表示对所有的数字进行的操作,最后输出的时候我们加上就可以了。
else if(op[0]=='A'){ else if(op[1]=='3'){ x=read(); all_tag+=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)); } }
直接输出第一个堆中该节点的堆顶。
else if(op[0]=='F'){ else if(op[1]=='2'){ x=read(); printf("%d\n",N.val[N.find(x)]+all_tag); } }
直接输出第二个堆的堆顶。
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; }