左偏树

BB

此时,一个菜逼路过 QAQ

 

Introduction

左偏树,顾名思义,就是一棵向左偏的树(大雾
左偏树,属于可并堆的一种,可以在O(logn)时间内完成插入、删除节点、合并堆等操作

 

Monkey King - luogu P1456

Description
一开始有n只孤独的猴子,然后他们要打m次架,每次打架呢,都会拉上自己朋友最牛叉的出来跟别人打,打完之后战斗力就会减半,每次打完架就会成为朋友(正所谓不打不相识o(∩_∩)o )。问每次打完架之后那俩猴子最牛叉的朋友战斗力还有多少,若朋友打架就输出-1.
范围:n, m <= 100000,可能存在多组输入
Sample Input

5
20
16
10
10
4
5
2 3
3 4
3 5
4 5
1 5

Sample Output

8
5
5
-1
10

Solution
接近裸的左偏树
“战力减半”需要先把节点删除,再插入

#include <bits/stdc++.h>
using namespace std;
const int N = 100000 + 15;

int ch[N][2], val[N], d[N], ft[N];

inline void init(int n) {
    for(int i = 1; i <= n; i++) {
        ft[i] = i;
        d[i] = 1;
        ch[i][0] = ch[i][1] = 0;
    }
}

inline int find(int x) {
    return ft[x] == x ? x : ft[x] = find(ft[x]);
}

inline int merge(int x, int y) {
    if(!x || !y) {
        return x | y;
    }
    if(val[x] < val[y]) {
        swap(x, y);
    }
    ch[x][1] = merge(ch[x][1], y);
    if(d[ch[x][0]] < d[ch[x][1]]) {
        swap(ch[x][0], ch[x][1]);
    }
    d[x] = d[ch[x][1]] + 1;
    return x;
}

int main() {
    int n, m;
    while(~scanf("%d", &n)) {
        init(n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &val[i]);
        }
        scanf("%d", &m);
        while(m--) {
            int u, v;
            scanf("%d%d", &u, &v);
            u = find(u), v = find(v);
            if(u == v) {
                puts("-1");
            } else {
                for(int k = 0; k < 2; k++) {
                    val[u] >>= 1;
                    int x = merge(ch[u][0], ch[u][1]);
                    ch[u][0] = ch[u][1] = 0;
                    int y = merge(u, x);
                    ft[u] = ft[x] = y;
                    swap(u, v);
                }
                u = find(u), v = find(v);
                int x = merge(u, v);
                ft[u] = ft[v] = x;
                printf("%d\n", val[x]);
            }
        }
    }
}

 

[APIO2012]派遣 - luogu P1552

Description
在这个帮派里,有一名忍者被称之为Master。除了Master以外,每名忍者都有且仅有一个上级。为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送。
现在你要招募一批忍者,并把它们派遣给顾客。你需要为每个被派遣的忍者支付一定的薪水,同时使得支付的薪水总额不超过你的预算。另外,为了发送指令,你需要选择一名忍者作为管理者,要求这个管理者可以向所有被派遣的忍者发送指令,在发送指令时,任何忍者(不管是否被派遣)都可以作为消息的传递人。管理者自己可以被派遣,也可以不被派遣。当然,如果管理者没有被排遣,你就不需要支付管理者的薪水。
你的目标是在预算内使顾客的满意度最大。这里定义顾客的满意度为派遣的忍者总数乘以管理者的领导力水平,其中每个忍者的领导力水平也是一定的。
写一个程序,给定每一个忍者i的上级Bi,薪水Ci,领导力Li,以及支付给忍者们的薪水总预算M,输出在预算内满足上述要求时顾客满意度的最大值。
范围:
1 ≤ N ≤ 100,000 忍者的个数;
1 ≤ M ≤ 1,000,000,000 薪水总预算; 0 ≤ Bi < i 忍者的上级的编号;
1 ≤ Ci ≤ M 忍者的薪水;
1 ≤ Li ≤ 1,000,000,000 忍者的领导力水平。
Sample Input

5 4
0 3 3
1 3 5
2 2 2
1 2 4
2 3 1

Sample Output

6

Solution 1 - 左偏树
不难想到就是枚举子树,对于每一棵子树,贪心的选小的元素,这样才能使得数量最多
因此,维护大根堆,使得队内元素和不超过M,合并时,如果超过M,就不断把根pop掉
由于每个元素只可能入堆一次,故总复杂度为O(nlogn)
另外,维护和和维护数量这一工作,推荐放在并查集上跑

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000 + 15;

int ch[N][2], d[N], val[N], ft[N];
int cnt[N], lzy[N];
ll sum[N];
vector<int> graph[N];
int l[N];

inline void init() {
    for(int i = 1; i < N; i++) {
        ft[i] = i;
        d[i] = 1;
        cnt[i] = 1;
    }
}

inline int find(int x) {
    return ft[x] == x ? x : ft[x] = find(ft[x]);
}

inline int merge(int x, int y) {
    if(!x || !y) {
        return x | y;
    }
    if(val[x] < val[y]) {
        swap(x, y);
    }
    ch[x][1] = merge(ch[x][1], y);
    if(d[ch[x][0]] < d[ch[x][1]]) {
        swap(ch[x][0], ch[x][1]);
    }

    d[x] = d[ch[x][1]] + 1;
    return x;
}

ll solve(int u, int k) {
    ll ans = 0;
    int rt = u;
    for(int v : graph[u]) {
        ans = max(ans, solve(v, k));
        int x = find(v);
        int nrt = merge(rt, x);
        sum[nrt] = sum[x] + sum[rt];
        cnt[nrt] = cnt[x] + cnt[rt];
        ft[x] = ft[rt] = ft[nrt] = nrt;
        rt = nrt;
    }
    // Pop
    //printf("? %lld\n", sum[rt]);
    while(sum[rt] > k) {
        int ls = ch[rt][0], rs = ch[rt][1];
        int y = merge(ls, rs);
        sum[y] = sum[rt] - val[rt];
        cnt[y] = cnt[rt] - 1;
        ft[y] = ft[rt] = y;
        ch[rt][0] = ch[rt][1] = 0;
        rt = y;
    }
    //printf("? %d %d %d\n", rt, cnt[rt], l[u]);
    ans = max(ans, 1LL * cnt[rt] * l[u]);
    return ans;
}

int main() {
    init();
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++) {
        int p;
        scanf("%d%d%d", &p, &val[i], &l[i]);
        sum[i] = val[i];
        if(p) {
            graph[p].push_back(i);
        }
    }
    ll ans = solve(1, k);
    printf("%lld\n", ans);
}

Solution 2 - set启发式合并(非正解!)
其实暴力也是可以的,set启发式合并总复杂度O(nlog^2n)
会TLE一个点,开O2可以全部AC

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000 + 15;

int c[N], l[N];
int ft[N];
ll sum[N];
multiset<int, greater<int> > st[N];
vector<int> graph[N];

inline int find(int x) {
    return ft[x] == x ? x : ft[x] = find(ft[x]);
}

inline void update(int x, int y, int k) {
    x = find(x), y = find(y);
    if(st[x].size() < st[y].size()) {
        swap(x, y);
    }
    ft[y] = x;
    for(int ele : st[y]) {
        st[x].emplace(ele);
    }
    st[y].clear();
    sum[x] += sum[y];
    while(!st[x].empty() && sum[x] > k) {
        multiset<int>::iterator it = st[x].begin();
        sum[x] -= *it;
        st[x].erase(it);
    }
}

inline ll solve(int u, int k) {
    ll ans = 0;
    for(int v : graph[u]) {
        ans = max(ans, solve(v, k));
        update(u, v, k);
    }
    int x = find(u);
    ans = max(ans, (ll)st[x].size() * l[u]);
    return ans;
}

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++) {
        int p;
        scanf("%d%d%d", &p, &c[i], &l[i]);
        ft[i] = i;
        if(c[i] <= k) {
            st[i].emplace(c[i]);
            sum[i] = c[i];
        }

        if(p) {
            graph[p].emplace_back(i);
        }
    }
    ll ans = solve(1, k);
    printf("%lld\n", ans);
}

 

「JLOI2015」城池攻占 - loj 2107

Description
小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池。这 n 个城池用 1 到 n 的整数表示。除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,其中 fi <i。也就是说,所有城池构成了一棵有根树。这 m 个骑士用 1 到 m 的整数表示,其中第 i 个骑士的初始战斗力为 si,第一个攻击的城池为 ci。
每个城池有一个防御值 hi,如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可以占领这座城池;否则占领失败,骑士将在这座城池牺牲。占领一个城池以后,骑士的战斗力将发生变化,然后继续攻击管辖这座城池的城池,直到占领 1 号城池,或牺牲为止。
除 1 号城池外,每个城池 i 会给出一个战斗力变化参数 ai;vi。若 ai =0,攻占城池 i 以后骑士战斗力会增加 vi;若 ai =1,攻占城池 i 以后,战斗力会乘以 vi。注意每个骑士是单独计算的。也就是说一个骑士攻击一座城池,不管结果如何,均不会影响其他骑士攻击这座城池的结果。
现在的问题是,对于每个城池,输出有多少个骑士在这里牺牲;对于每个骑士,输出他攻占的城池数量。

5 5
50 20 10 10 30
1 1 2
2 0 5
2 0 -10
1 0 10
20 2
10 3
40 4
20 4
35 5

Sample Output

2
2
0
0
0
1
1
3
1
1

Solution
可以发现可以对骑士在树上维护堆,这样DFS遍历树时,先将子树剩余的堆合并后,再将此时堆中战力小于防御值的元素弹走,同时记录此时战死在此的骑士数量,再更新堆中的元素的大小
合并堆用左偏树,弹走元素优先弹走最小所以是小根堆,更新堆中所有元素大小是同时更新的,且不会改变相对大小(题目中的操作为同时加上一个数,或者同时乘上一个 正数),所以可以用类似线段树的懒惰标记搞搞
最后需要考虑的是如何统计每个骑士攻占了几个点,由于骑士一直往上爬,所以战死的点的深度减去原深度即是答案,这在弹走时同时统计即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 300000 + 15;

int a[N];
ll v[N], h[N], val[N];
ll lzyAdd[N], lzyMul[N];
int dpt[N], ed[N], c[N];
int mp[N];
int ch[N][2], d[N], cnt[N];
vector<int> graph[N];

inline void init() {
    for (int i = 1; i < N; i++) {
        d[i] = 1;
        lzyMul[i] = 1;
    }
}

inline void pushDown(int x) {
    if (lzyAdd[x] != 0 || lzyMul[x] != 1) {
        for (int k = 0; k < 2; k++) {
            val[ch[x][k]] = lzyMul[x] * val[ch[x][k]] + lzyAdd[x];
            lzyMul[ch[x][k]] *= lzyMul[x];
            lzyAdd[ch[x][k]] = lzyMul[x] * lzyAdd[ch[x][k]] + lzyAdd[x];
        }
        lzyAdd[x] = 0;
        lzyMul[x] = 1;
    }
}

inline int merge(int x, int y) {
    if (!x || !y) {
        return x | y;
    }
    if (val[x] > val[y]) {
        swap(x, y);
    }
    pushDown(x);
    ch[x][1] = merge(ch[x][1], y);
    if (d[ch[x][0]] < d[ch[x][1]]) {
        swap(ch[x][0], ch[x][1]);
    }
    d[x] = d[ch[x][1]] + 1;
    return x;
}

inline void dfs(int u) {
    int rt = mp[u];
    for (int v : graph[u]) {
        dpt[v] = dpt[u] + 1;
        dfs(v);
        rt = merge(rt, mp[v]);
    }
    // printf("u = %d\n", u);
    while (rt && val[rt] < h[u]) {
        // printf("rt = %d, val = %lld\n", rt, val[rt]);
        cnt[u]++;
        ed[rt] = u;
        pushDown(rt);
        rt = merge(ch[rt][0], ch[rt][1]);
    }
    // printf("rt = %d, val = %lld\n", rt, val[rt]);
    mp[u] = rt;
    if (a[u] == 0) {
        val[rt] += v[u];
        lzyAdd[rt] += v[u];
    } else {
        val[rt] *= v[u];
        lzyMul[rt] *= v[u];
        lzyAdd[rt] *= v[u];
    }
}

int main() {
    init();
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &h[i]);
    }
    for (int i = 2; i <= n; i++) {
        int p;
        scanf("%d%d%lld", &p, &a[i], &v[i]);
        graph[p].push_back(i);
    }
    for (int i = 1; i <= m; i++) {
        scanf("%lld%d", &val[i], &c[i]);
        mp[c[i]] = merge(mp[c[i]], i);
    }
    dpt[1] = 1;
    dfs(1);
    for (int i = 1; i <= n; i++) {
        printf("%d\n", cnt[i]);
    }
    for (int i = 1; i <= m; i++) {
        printf("%d\n", dpt[c[i]] - dpt[ed[i]]);
    }
}