左偏树是一种支持log(n)合并的堆式数据结构。
定义:
外节点:不同时拥有左右儿子的节点
x的距离(dist):x到达子树中(包括自己)最近的外节点的距离,特别定义空节点的dist为-1
基本性质和结论:
除了满足堆的每个节点都 大于 或者 小于 左右儿子的性质,还满足(大概不全)
1、左偏性:对于每个节点, dist[ls]>=dist[rs], ls=leftson rs=rightson
2、dist[x] = dist[rs] + 1
3、n个节点的左偏树的dist[root] = log_2(n)
基本操作merge(注:以下均为小根堆)
总的来说,merge总节点数不变,但需要同时维护堆的性质和左偏性。可以通过不断递归以权值为标准把堆分成很多单元,再向上合并,最后再维护左偏性。
流程:
1、对于两个左偏树的根节点x, y,v[x] < v[y]
2、用以rs[x]为根的左偏树去和以y为根的左偏树merge(向下递归相当于把两个左偏树拆成很多片),再让rs[x] = 合并后的结果
3、重复以上12操作,如果!x||!y为空节点直接返回 x+y(每次都是y不变,x = rs[x],到最后“rs[x] = merge(rs[x], y);”中rs[x]是0、y不是0,然后把y放在rs[x]上)
4、维护左偏性:如果dist[rs[x]] > dist[ls[x]],则swap(rs[x], ls[x]);
int v[maxn], ls[maxn], rs[maxn], dist[maxn];
int merge(int x, int y)
{
if (!x || !y)return x + y;
if (v[x] > v[y])swap(x, y);//ensure v[x]<v[y]
rs[x] = merge(rs[x], y);
if (dist[rs[x]] > dist[ls[x]])swap(rs[x], ls[x]);
dist[x] = dist[rs[x]] + 1;
return x;//x和y之前可能换过
}
对于这题
插入:把大小为1的左偏树合并上去
合并:字面意思
删除:把根删除,合并左右两个子树。(由于这题用了并查集,根节点会作为路径压缩的终点,所以删除以后要fa[x] = merge(ls[x],rs[x]))
代码:
#include<bits/stdc++.h>
#define ll long long
#define fastio ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
double pi = acos(-1);
const double eps = 1e-6;
const int maxn = 1e5 + 10;
const ll mod = 1e9 + 7;
const int inf = 1e11;
int v[maxn], ls[maxn], rs[maxn], dist[maxn];
int merge(int x, int y)
{
if (!x || !y)return x + y;
if (v[x] > v[y])swap(x, y);//ensure v[x]<v[y]
rs[x] = merge(rs[x], y);
if (dist[ls[x]] < dist[rs[x]])swap(rs[x], ls[x]);
dist[x] = dist[rs[x]] + 1;
return x;
}
int fa[maxn];
int anc(int x) { return x == fa[x] ? x : fa[x] = anc(fa[x]); }
int main()
{
fastio;
memset(v, -1, sizeof(v));
dist[0]=-1;
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++)cin >> v[i], fa[i] = i;
while (q--)
{
int o;
cin >> o;
if (o == 1)
{
int x, y;
cin >> x >> y;
if (v[x] == -1 || v[y] == -1)continue;
x = anc(x), y = anc(y);
if (x != y)
fa[x] = fa[y] = merge(x, y);
}
else
{
int x;
cin >> x;
if (v[x] == -1)
{
cout << -1 << endl;
continue;
}
x = anc(x);
cout << v[x] << endl;
v[x] = -1;
fa[x] = fa[ls[x]] = fa[rs[x]] = merge(ls[x], rs[x]);
//ls[x] = rs[x] = dist[x] = 0;
}
}
return 0;
}
P1456 Monkey King
模版题,把顶部除2之后就不能保证堆的性质,所以要把它先删除,合并左右儿子,再单独把它合并进去(记得初始化左右儿子)
#include<bits/stdc++.h>
#define ll long long
#define fastio ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
const int maxn = 1e5 + 10;
int v[maxn], ls[maxn], rs[maxn], dist[maxn];
int merge(int x, int y)
{
if (!x || !y)return x + y;
if (v[x] < v[y])swap(x, y);
rs[x] = merge(rs[x], y);
if (dist[ls[x]] < dist[rs[x]])swap(rs[x], ls[x]);
dist[x] = dist[rs[x]] + 1;
return x;
}
int fa[maxn];
int anc(int x) { return x == fa[x] ? x : fa[x] = anc(fa[x]); }
int Make_merge(int x, int y)
{
fa[x] = fa[y] = merge(x, y);
return fa[x];
}
int main()
{
fastio;
int n;
while (cin >> n)
{
dist[0] = -1;
for (int i = 1; i <= n; i++)
cin >> v[i], fa[i] = i, ls[i] = rs[i] = dist[i] = 0;
int q;
cin >> q;
int a, b;
while (q--)
{
int x, y;
cin >> x >> y;
x = anc(x), y = anc(y);
if (x == y)
{
cout << -1 << endl;
continue;
}
a = Make_merge(ls[x], rs[x]), b = Make_merge(ls[y], rs[y]);
v[x] /= 2;
v[y] /= 2;
rs[x] = ls[x] = rs[y] = ls[y] = 0;
a = Make_merge(a, x);
b = Make_merge(b, y);
cout << v[Make_merge(a, b)] << "
";
}
}
return 0;
}