(可并堆)p3377 左偏树 + P1456 Monkey King

左偏树是一种支持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)] << "\n";
        }
    }
    return 0;
}
posted @ 2021-08-26 18:13  Lecoww  阅读(46)  评论(0编辑  收藏  举报