左偏树/可并堆 学习笔记

以前粗浅地学习过这个数据结构,但是学得太浅学了跟没学一样。现在打算在学一遍,也许现在也是学了跟没学一样qaq

推荐博客:https://blog.csdn.net/wang3312362136/article/details/80615874

https://blog.csdn.net/pengwill97/article/details/82874235 (代码很好)

可并堆顾名思义就是可以合并的堆,可以理解为可以合并两个优先队列的数据结构,可并堆代码好写时间复杂度也十分优秀(大佬说是可以被卡成O(n)的)是一个不错的数据结构。可并堆具有堆性质和左偏性质。常见的操作有 ①合并两个堆 ②查询某个数所在堆 ③查询/删除堆顶元素。

模板题:洛谷P3377 左偏树

代码是参考上面大佬的(读书人的事能叫...咳咳)。但是大佬有一点错误:在查找某个元素的所属堆的时候是用的暴力向上跳的方式,这样一步步跳会被卡到O(n),在洛谷提交会被卡最后一组数据。优化办法是查找的过程应该顺便路径压缩,并且要注意采用了路径压缩那么pop元素的时候pop掉的点要指向新的根

#include <bits/stdc++.h>
using namespace std;
const int nmax = 1e6 + 7;
const int INF = 0x3f3f3f3f;
struct node {
    int val, lc, rc, dis, fa;
}tree[nmax];
int tot = 0;
int n, m;
void init(int x) {
    for (int i = 0; i <= x; ++i) {
        tree[i].lc = tree[i].rc = tree[i].dis = 0;
        tree[i].fa = i;  //一开始每个元素都是堆顶 
    }
}

int merge(int x, int y) {
    if (x == 0) return y;
    if (y == 0) return x;
    if (tree[x].val > tree[y].val || (tree[x].val == tree[y].val && x > y) )
        swap(x, y);  //注意这里的合并优先级,像是优先队列的优先级 
    tree[x].rc = merge(tree[x].rc, y);
    tree[tree[x].rc].fa = x;
    if (tree[tree[x].rc].dis > tree[tree[x].lc].dis)
        swap(tree[x].rc, tree[x].lc);
    tree[x].dis = tree[x].rc == 0 ? 0 : tree[tree[x].rc].dis + 1;
    return x;
}
int findset(int x) {
    return tree[x].fa==x ? x : tree[x].fa=findset(tree[x].fa); //fa=x的才是堆顶 
}
//int findset(int x) {
//    while (tree[x].fa != x) { //fa=x的才是堆顶 
//        x = tree[x].fa;
//    }
//    return x;
//}
int add(int val, int x) {  //往x堆新增元素val 
    tree[tot].lc = tree[tot].rc = tree[tot].dis = 0;
    tree[tot++].val = val;
    return merge(tot - 1, x);
}
int del(int x) {
    int l = tree[x].lc, r = tree[x].rc;
    tree[x].fa = tree[x].lc = tree[x].rc = tree[x].dis = 0;
    tree[x].val = -INF;
    tree[l].fa = l, tree[r].fa = r;
    return tree[x].fa=merge(l, r);  //加入了路径压缩,pop掉的点要指向新的根 
}

int build() {
    queue<int> q;
    for (int i = 1; i <= n; ++i) q.push(i);
    while (!q.empty()) {
        if (q.size() == 1) break;
        else {
            int x = q.front(); q.pop();
            int y = q.front(); q.pop();
            q.push(merge(x, y));
        }
    }
    int finally = q.front(); q.pop();
    return finally;
}

int main() 
{
    scanf("%d %d", &n, &m);
    init(n);
    for (int i = 1; i <= n; ++i) scanf("%d", &tree[i].val);
    int op, a, b;
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d %d", &a, &b);
            int xx = findset(a), yy = findset(b);
            if (tree[a].val == -INF || tree[b].val == -INF || xx == yy) {
                continue;
            } else {
                merge(xx, yy);
            }
        } else {
            scanf("%d", &a);
            if (tree[a].val == -INF) {
                printf("-1\n");
            } else {
                int tmp = findset(a);
                printf("%d\n", tree[tmp].val);
                del(tmp);
            }
        }
    }
    return 0;
}
View Code

BZOJ 2809 dispatching

题意:给定n个点的树,每个点有代价b和价值c,选一个根x然后在以x为根的子树选尽量多的点且这些点代价总和小于等于m,得到的价值是c[x]*siz[x](x点价值乘上选的点总数)。

解法:这道题非常好,刚好用来练手可并堆。一个显然正确的做法是,在树上dfs,dfs到当前点x就计算以在x子树选的最大值,那么如果当前子树花费总和大于m的话当然是按照花费从大到小把点剔除掉,直至花费小于m就是答案。再思考发现这个过程自下往上是可以连续考虑的(这里的意思是儿子子树不用的点父亲子树也一定不会用到因为m是固定的!)。那么就可以搜索过程维护一个堆,然后堆自下往上合并,那么就要用到左偏树优化了。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
const int INF = 0x3f3f3f3f;
typedef long long LL;
struct node {
    int val, lc, rc, dis, fa;
}tree[N];
vector<int> G[N];
int tot = 0;
int n, m,rt,a[N],b[N],c[N];
LL ans=0;
void init(int x) {
    for (int i = 0; i <= x; ++i) {
        tree[i].lc = tree[i].rc = tree[i].dis = 0;
        tree[i].fa = i;  //一开始每个元素都是堆顶 
    }
}

int merge(int x, int y) {
    if (x == 0) return y;
    if (y == 0) return x;
    if (tree[x].val < tree[y].val || (tree[x].val == tree[y].val && x < y) )
        swap(x, y);  //注意这里的合并优先级,像是优先队列的优先级 
    tree[x].rc = merge(tree[x].rc, y);
    tree[tree[x].rc].fa = x;
    if (tree[tree[x].rc].dis > tree[tree[x].lc].dis)
        swap(tree[x].rc, tree[x].lc);
    tree[x].dis = tree[x].rc == 0 ? 0 : tree[tree[x].rc].dis + 1;
    return x;
}
int findset(int x) {  //路径压缩 
    return tree[x].fa==x ? x : tree[x].fa=findset(tree[x].fa); //fa=x的才是堆顶 
}
//int findset(int x) {
//    while (tree[x].fa != x) { //fa=x的才是堆顶 
//        x = tree[x].fa;
//    }
//    return x;
//}
int add(int val, int x) {  //往x堆新增元素val 
    tree[tot].lc = tree[tot].rc = tree[tot].dis = 0;
    tree[tot++].val = val;
    return merge(tot - 1, x);
}
int del(int x) {
    int l = tree[x].lc, r = tree[x].rc;
    tree[x].fa = tree[x].lc = tree[x].rc = tree[x].dis = 0;
    tree[x].val = -INF;
    tree[l].fa = l, tree[r].fa = r;
    return tree[x].fa=merge(l, r);  //加入了路径压缩,pop掉的点要指向新的根 
}

int build() {
    queue<int> q;
    for (int i = 1; i <= n; ++i) q.push(i);
    while (!q.empty()) {
        if (q.size() == 1) break;
        else {
            int x = q.front(); q.pop();
            int y = q.front(); q.pop();
            q.push(merge(x, y));
        }
    }
    int finally = q.front(); q.pop();
    return finally;
}

LL sum[N]; int siz[N];
void dfs(int x) {
    sum[x]+=b[x]; siz[x]++;
    for (int i=0;i<G[x].size();i++) {
        int y=G[x][i];
        dfs(y);
        sum[x]+=sum[y]; siz[x]+=siz[y];
        int fx=findset(x),fy=findset(y);
        merge(fx,fy);
    }
    while (sum[x]>m) {
        int fx=findset(x);
        sum[x]-=tree[fx].val; del(fx);
        siz[x]--;
    }
    ans=max(ans,(LL)siz[x]*c[x]);
}

int main() 
{
    scanf("%d %d", &n, &m);
    for (int i=1;i<=n;i++) {
        scanf("%d%d%d",&a[i],&b[i],&c[i]);
        if (a[i]==0) rt=i;
        else G[a[i]].push_back(i);
    }
    init(n);
    for (int i = 1; i <= n; ++i) tree[i].val=b[i];
    dfs(rt);
    cout<<ans<<endl;
    return 0;
}
View Code

BZOJ 1367 sequence

题意:给定序列a1~an,求一个递增序列b1~bn,使得abs(a1-b1)+abs(a2-b2)+...abs(an-bn)最小。

解法:这是https://wenku.baidu.com/view/20e9ff18964bcf84b9d57ba1.html这篇论文里的题目。

猜你喜欢

转载自www.cnblogs.com/clno1/p/11423254.html