题解 P3273 【[SCOI2011]棘手的操作】
ouuan
2019-04-04 08:26:11
这题目前题解中所有左偏树做法均可被卡(一条链,不停单点询问链底端)。
其实这题是有左偏树正解的,就是[这篇题解](https://www.luogu.org/blog/user17667/solution-p3273)的做法,然而他是暴力向上跳找根的,所以还是可以被卡。
首先,找一个节点所在堆的堆顶要用并查集,而不能暴力向上跳。
再考虑单点查询,若用普通的方法打标记,就得查询点到根路径上的标记之和,最坏情况下可以达到 $\mathcal O(n)$ 的复杂度。如果只有堆顶有标记,就可以 $\mathcal O(\log n)$(只有路径压缩的并查集复杂度)地查询了,但如何做到呢?
可以用类似启发式合并的方式,每次合并的时候把较小的那个堆标记暴力下传到每个节点,然后把较大的堆的标记作为合并后的堆的标记。由于合并后有另一个堆的标记,所以较小的堆下传标记时要下传其标记减去另一个堆的标记。由于每个节点每被合并一次所在堆的大小至少乘二,所以每个节点最多被下放 $\mathcal O(\log n)$ 次标记,暴力下放标记的总复杂度就是 $\mathcal O(n\log n)$。
再考虑单点加,先删除,再更新,最后插入即可。
然后是全局最大值,可以用一个平衡树/左偏树/multiset 来维护每个堆的堆顶。
所以,每个操作分别如下:
1. 暴力下传点数较小的堆的标记,合并两个堆,更新 size、tag,在 multiset 中删去合并后不在堆顶的那个原堆顶。
2. 删除节点,更新值,插入回来,更新 multiset。需要分删除节点是否为根来讨论一下。
3. 堆顶打标记,更新 multiset。
4. 打全局标记。
5. 查询值+堆顶标记+全局标记。
6. 查询根的值+堆顶标记+全局标记。
7. 查询 multiset 最大值+全局标记。
参考代码:
```cpp
#include <iostream>
#include <cstdio>
#include <set>
#include <cctype>
#include <algorithm>
using namespace std;
int read()
{
int out=0,f=1;
char c;
for (c=getchar();!isdigit(c)&&c!='-';c=getchar());
if (c=='-') { f=-1; c=getchar(); }
for (;isdigit(c);c=getchar()) out=out*10+c-'0';
return out*f;
}
const int N=300010;
struct Node
{
int val,ch[2],d,fa;
} t[N];
int& rs(int x);
int merge(int x,int y);
void pushup(int x);
void pushdown(int x,int y);
int find(int x);
int n,m,f[N],tag[N],siz[N],delta;
char op[10];
multiset<int> s;
int main()
{
int i,x,y;
n=read();
for (i=1;i<=n;++i)
{
t[i].val=read();
f[i]=i;
siz[i]=1;
s.insert(t[i].val);
}
m=read();
while (m--)
{
scanf("%s",op);
if (op[0]=='U')
{
x=find(read());
y=find(read());
if (x!=y)
{
if (siz[x]>siz[y]) swap(x,y);
pushdown(x,tag[x]-tag[y]);
f[x]=f[y]=merge(x,y);
if (f[x]==x)
{
s.erase(s.find(t[y].val+tag[y]));
tag[x]=tag[y];
siz[x]+=siz[y];
tag[y]=siz[y]=0;
}
else
{
s.erase(s.find(t[x].val+tag[y]));
siz[y]+=siz[x];
tag[x]=siz[x]=0;
}
}
}
else if (op[0]=='A')
{
if (op[1]=='1')
{
x=read();
if (x==find(x))
{
t[t[x].ch[0]].fa=t[t[x].ch[1]].fa=0;
y=merge(t[x].ch[0],t[x].ch[1]);
s.erase(s.find(t[x].val+tag[x]));
t[x].val+=read();
t[x].fa=t[x].ch[0]=t[x].ch[1]=0;
t[x].d=1;
f[x]=f[y]=merge(x,y);
s.insert(t[f[x]].val+tag[x]);
if (f[x]==y)
{
tag[y]=tag[x];
siz[y]=siz[x];
tag[x]=siz[x]=0;
}
}
else
{
t[t[x].ch[0]].fa=t[t[x].ch[1]].fa=t[x].fa;
t[t[x].fa].ch[x==t[t[x].fa].ch[1]]=merge(t[x].ch[0],t[x].ch[1]);
t[x].val+=read();
t[x].fa=t[x].ch[0]=t[x].ch[1]=0;
t[x].d=1;
y=find(x);
f[x]=f[y]=merge(x,y);
if (f[x]==x)
{
s.erase(s.find(t[y].val+tag[y]));
s.insert(t[x].val+tag[y]);
tag[x]=tag[y];
siz[x]=siz[y];
tag[y]=siz[y]=0;
}
}
}
else if (op[1]=='2')
{
x=find(read());
s.erase(s.find(t[x].val+tag[x]));
tag[x]+=read();
s.insert(t[x].val+tag[x]);
}
else delta+=read();
}
else
{
if (op[1]=='1')
{
x=read();
printf("%d\n",t[x].val+tag[find(x)]+delta);
}
else if (op[1]=='2')
{
x=find(read());
printf("%d\n",t[x].val+tag[x]+delta);
}
else printf("%d\n",*s.rbegin()+delta);
}
}
return 0;
}
int& rs(int x) //一种比较清奇的左偏树写法,无需交换左右儿子,把dist较小的视作右儿子即可
{
return t[x].ch[t[t[x].ch[1]].d<t[t[x].ch[0]].d];
}
int merge(int x,int y)
{
if (!x||!y) return x|y;
if (t[x].val<t[y].val) swap(x,y);
t[rs(x)=merge(rs(x),y)].fa=x;
pushup(x);
return x;
}
void pushup(int x) //一种比较清奇的删节点写法,merge之后递归地pushup即可
{
if (!x) return;
if (t[x].d!=t[rs(x)].d+1)
{
t[x].d=t[rs(x)].d+1;
pushup(t[x].fa);
}
}
void pushdown(int x,int y)
{
if (!x) return;
t[x].val+=y;
pushdown(t[x].ch[0],y);
pushdown(t[x].ch[1],y);
}
int find(int x)
{
return x==f[x]?x:f[x]=find(f[x]);
}
```