一、题目
二、解法
维护一个左偏树和一个并查集,这道题的关键是使用惰性删除法,然而我讲不清楚,直接看代码吧(详细注释)。
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 300005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
struct node
{
int no,v,ls,rs,dis;
/*
左偏树上的节点
no:当前点对应并查集上的编号
v:权值 ls,rs:左右儿子 dis:距离
*/
}t[2*M];
struct data
{
int v,ver,no;
/*
并查集的代表节点,用于F3
v:权值
ver:对应的并查集版本
no:对应的并查集编号
*/
bool operator < (const data &b) const
{
return v<b.v;
}
}tmp;
int n,m,cnt,vers[M],rt[M],la[M],top[M],siz[M],ver[M];char s[6];
/*
cnt:左偏树节点的个数
vers:并查集的版本
ver:并查集上单个点在左偏树上的版本
la:并查集的修改标记
top:并查集在左偏树上的根
siz:并查集的大小
*/
priority_queue<data> q;
int merge(int x,int y)//左偏树的合并
{
if(!x || !y) return x|y;
if(t[x].v<t[y].v) swap(x,y);
t[x].rs=merge(t[x].rs,y);
if(t[t[x].ls].dis<t[t[x].rs].dis) swap(t[x].ls,t[x].rs);
t[x].dis=t[t[x].rs].dis+1;
return x;
}
int find(int x)//并查集的根
{
if(x^rt[x]) rt[x]=find(rt[x]);
return rt[x];
}
void push(int x,int v)//整棵树做修改
{
if(!x) return ;
t[x].v+=v;
push(t[x].ls,v);
push(t[x].rs,v);
}
int fake(int x)//判断这个点是否被惰性删除过,即判断版本
{
return ver[t[x].no]!=x;
}
signed main()
{
t[0].dis=-1;
cnt=n=read();
for(int i=1;i<=n;i++)//初始化
{
t[i].v=read();
t[i].no=i;
vers[i]=siz[i]=1;
rt[i]=top[i]=ver[i]=i;
tmp.no=i;tmp.v=t[i].v;tmp.ver=1;
q.push(tmp);
}
m=read();
for(int i=1;i<=m;i++)
{
scanf("%s",s);
if(s[0]=='U')//合并
{
int u=read(),v=read();
u=find(u);v=find(v);//直接对并查集的根操作
if(u==v) continue;
if(siz[u]<siz[v]) swap(u,v);//启发式合并
push(top[v],la[v]-la[u]);//需要共用标记,所以先做修改
top[u]=merge(top[u],top[v]);//左偏树合并
rt[v]=u;
siz[u]+=siz[v];
vers[u]++;//u更新版本
vers[v]=0;//v的所有版本作废
tmp.no=u;tmp.ver=vers[u];
tmp.v=t[top[u]].v+la[u];
q.push(tmp);//重新把代表点插入优先队列中
}
if(s[0]=='A' && s[1]=='1')//单点修改
{
int u=read(),v=read();
cnt++;//新建一个点,存改变后的权值
t[cnt].no=u;
t[cnt].v=t[ver[u]].v+v;
ver[u]=cnt;//更新当前点的版本
u=find(u);
top[u]=merge(top[u],cnt);//左偏树上合并
while(fake(top[u]))//看根是否被惰性删除过
top[u]=merge(t[top[u]].ls,t[top[u]].rs);
vers[u]++;//更新u的版本
tmp.no=u;tmp.ver=vers[u];
tmp.v=t[top[u]].v+la[u];
q.push(tmp);//重新把代表点插入优先队列中
}
if(s[0]=='A' && s[1]=='2')//并查集修改
{
int u=read(),v=read();
u=find(u);
la[u]+=v;//直接对整个并查集的标记修改
vers[u]++;//更新u的版本
tmp.no=u;tmp.ver=vers[u];
tmp.v=t[top[u]].v+la[u];
q.push(tmp);//重新把代表点插入优先队列中
}
if(s[0]=='A' && s[1]=='3')//全局修改
{
la[0]+=read();//直接加呗
}
if(s[0]=='F' && s[1]=='1')
{
int u=read(),v;
v=find(u);
printf("%d\n",t[ver[u]].v+la[v]+la[0]);//当前点权值+并查集标记+全局标记
}
if(s[0]=='F' && s[1]=='2')
{
int u=read();
u=find(u);
printf("%d\n",t[top[u]].v+la[u]+la[0]);//并查集最值+并查集标记+全局标记
}
if(s[0]=='F' && s[1]=='3')
{
while(!q.empty())
{
tmp=q.top();
if(tmp.ver!=vers[tmp.no]) q.pop();//惰性删除,判断版本对不对
else break;
}
printf("%d\n",tmp.v+la[0]);//全局最值+全局标记
}
}
}