左偏树(左翼堆)
先附左偏树常见错误,警示自己:
-
开始时
, 。 -
合并时更新右儿子的父亲。
-
两个返回值:空节点返回
,否则返回 。 -
先交换儿子,再判断是否为右儿子加 。 -
删除中间节点时是将这个节点的根和儿子树合并。
一般的二叉堆,要对其进行合并操作,最朴素的方法是每个元素逐一与另一个堆合并,即使是启发式,也是
然而,左偏树可以做到
这里,我们引入一个变量
证明:一个有
假设根节点的
所以
这样一来,复杂度基于高度的合并就是正确的了。
左偏树的核心操作在于合并,而合并操作的核心在于
这是一种递归式的操作。
每次对比两个参数,令更小的(视情况而定)作为新的根节点,较大的根节点再与新根节点的右儿子合并(因为树是左偏的,右子树才是决定
点击查看代码
#include <bits/stdc++.h>
using namespace std;
template <typename T> inline void read(T &x)
{
x = 0; bool f = 0; char ch = getchar();
while('0' > ch || ch > '9') { if(ch == '-') f = !f; ch = getchar(); }
while('0' <= ch && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
x = f ? -x : x; return;
}
template <typename T> inline void print(T x)
{
if(x < 0) putchar('-'), x = -x;
if(x > 9) print(x / 10);
putchar(x % 10 + '0'); return;
}
const int N = 1e5 + 10;
int n, m;
int a[N], fa[N], lc[N], rc[N], dist[N], del[N];
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int merge(int x, int y)
{
if(!x || !y) return x + y;
if(a[x] > a[y] || a[x] == a[y] && x > y) swap(x, y);
rc[x] = merge(rc[x], y);
fa[rc[x]] = x;
if(dist[lc[x]] < dist[rc[x]]) swap(lc[x], rc[x]);
dist[x] = dist[rc[x]] + 1;
return x;
}
void Pop(int x)
{
del[x] = 1;
fa[lc[x]] = lc[x], fa[rc[x]] = rc[x];
fa[x] = fa[lc[x]] = fa[rc[x]] = merge(lc[x], rc[x]);
lc[x] = rc[x] = dist[x] = 0;
}
int main()
{
dist[0] = -1;
read(n); read(m);
for(int i = 1; i <= n; i ++) read(a[i]), fa[i] = i;
int op, x, y;
for(int i = 1; i <= m; i ++)
{
read(op);
if(op == 1)
{
read(x); read(y);
if(del[x] || del[y]) continue;
x = find(x); y = find(y);
if(x != y) fa[x] = fa[y] = merge(x, y);
}
else if(op == 2)
{
read(x);
if(del[x]) print(-1), putchar('\n');
else
{
x = find(x);
print(a[x]);
putchar('\n');
Pop(x);
}
}
}
return 0;
}
接下来是几道例题。
这题与板题的不同点在于没有删除(其实有,但是后面又加进来了),但是注意合并操作比较多,不要遗漏
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], dist[N], lc[N], rc[N], fa[N];
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int merge(int x, int y)
{
if(!x || !y) return x + y;
if(a[x] < a[y]) swap(x, y);
rc[x] = merge(rc[x], y);
fa[rc[x]] = x;
if(dist[lc[x]] < dist[rc[x]]) swap(lc[x], rc[x]);
dist[x] = dist[rc[x]] + 1;
return x;
}
void Pop(int x)
{
a[x] >>= 1;
fa[lc[x]] = lc[x], fa[rc[x]] = rc[x];
int rt;
fa[lc[x]] = fa[rc[x]] = rt = merge(lc[x], rc[x]);
lc[x] = rc[x] = dist[x] = 0;
fa[x] = fa[rt] = merge(rt, x);
}
int main()
{
while(scanf("%d", &n) != EOF)
{
dist[0] = -1;
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), fa[i] = i;
scanf("%d", &m);
int x, y;
for(int i = 1; i <= m; i ++)
{
scanf("%d %d", &x, &y);
x = find(x); y = find(y);
if(x == y)
{
printf("-1\n");
continue;
}
Pop(x); Pop(y);
x = find(x), y = find(y);
fa[x] = fa[y] = merge(x, y);
printf("%d\n", a[fa[x]]);
}
for(int i = 1; i <= n; i ++)
{
lc[i] = rc[i] = dist[i] = fa[i] = 0;
}
}
return 0;
}
这题多了一个修改中间值的操作,只要将这个数的子树都分离出去,改一下大树的结构(
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
template <typename T> inline void read(T &x)
{
x = 0; bool f = 0; char ch = getchar();
while('0' > ch || ch > '9') { if(ch == '-') f = !f; ch = getchar(); }
while('0' <= ch && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
x = f ? -x : x; return;
}
const int N = 2e6 + 10;
int T, W, K;
int n, m;
int a[N], lc[N], rc[N], fa[N], dist[N];
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int merge(int x, int y)
{
if(!x || !y) return x + y;
if(a[x] < a[y] || a[x] == a[y] && x > y) swap(x, y);
rc[x] = merge(rc[x], y);
fa[rc[x]] = x;
if(dist[lc[x]] < dist[rc[x]]) swap(lc[x], rc[x]);
dist[x] = dist[rc[x]] + 1;
return x;
}
void pushup(int x)
{
if(!x) return;
if(dist[lc[x]] < dist[rc[x]]) swap(lc[x], rc[x]);
if(dist[x] != dist[rc[x]] + 1)
{
dist[x] = dist[rc[x]] + 1;
pushup(fa[x]);
}
}
void ins(int x)
{
fa[lc[x]] = lc[x], fa[rc[x]] = rc[x];
int rt;
rt = fa[lc[x]] = fa[rc[x]] = merge(lc[x], rc[x]);
lc[x] = rc[x] = dist[x] = 0;
pushup(fa[x]);
x = find(x);
fa[x] = fa[rt] = merge(x, rt);
}
signed main()
{
read(T); read(W); read(K);
for(int ii = 1; ii <= T; ii ++)
{
dist[0] = -1;
read(n); read(m);
for(int i = 1; i <= n; i ++) read(a[i]), fa[i] = i;
int op, x, y, mx = 0, sum = 0;
for(int i = 1; i <= m; i ++)
{
read(op);
if(op == 2)
{
read(x);
a[x] = 0;
ins(x);
}
else if(op == 3)
{
read(x); read(y);
x = find(x);
a[x] = max(a[x] - y, (int)0);
ins(x);
}
else if(op == 4)
{
read(x); read(y);
x = find(x); y = find(y);
if(x != y) fa[x] = fa[y] = merge(x, y);
}
}
for(int i = 1; i <= n; i ++)
{
int t = find(i);
if(t != i) continue;
sum += a[t];
mx = max(mx, a[t]);
}
if(W == 2) sum -= mx;
else if(W == 3) sum += mx;
if(sum == 0) printf("Gensokyo");
else if(sum > K) printf("Hell");
else printf("Heaven");
printf(" %lld\n", sum);
for(int i = 0; i <= n; i ++) a[i] = lc[i] = rc[i] = dist[i] = fa[i] = 0;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】