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;
}