派遣「APIO2012」(左偏树)

派遣「APIO2012」(左偏树)

 

 

显然这种上下级的关系构成一个树形结构。

我们可以枚举一个管理者,然后再来看看他的满意度水平到底有多高。

因为要求这个管理者可以向所有人发送指令,所以他只能选他的子树中的点。

把他所有选的人放在同一个集合内,要求总薪水小于M且人数最多(因为满意度只和人数和管理者的领导力有关)。

那我们就得到一个思路:把所有子树内的人放到一个集合里,然后如果总薪水大于M,就把薪水最大的人干掉,直到薪水小于M。

如果这个集合是个堆就可以在$O(1)$的复杂度内找到薪水最大的人,并在$O(log{n})$的复杂度内干掉他。

可是这样做总复杂度是$O(n^2)$还是太慢。

考虑优化。

我们发现每个人最终的集合和它的孩子的集合是有大部分重复的,用线段树合并的思想,我们可以把他的儿子的集合并到他的上面。

这就要用到可并堆了(这里用左偏树)。

然后就可以在$O(n\log{n})$的复杂度内解决问题了。

具体实现的时候维护一个size和一个sum分别代表一个人的集合的大小和总薪水。

再维护一个root代表堆顶。

这是树上左偏树的基本套路。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll N = 100010;
struct Leftist_Tree{
    ll dist, val, ls, rs;
}tr[N];
struct node{
    ll pre, to;
}edge[N << 1];
ll head[N], tot;
ll n, m;
ll c[N], w[N], ans;
ll rt[N], sum[N], sz[N];
void add(ll u, ll v) {
    edge[++tot] = node{head[u], v};
    head[u] = tot;
}
ll Merge(ll u, ll v) {
    if (!u || !v) return u | v;
    if (tr[u].val < tr[v].val) swap(u, v);
    tr[u].rs = Merge(tr[u].rs, v);
    if (tr[tr[u].ls].dist < tr[tr[u].rs].dist) swap(tr[u].ls, tr[u].rs);
    tr[u].dist = tr[tr[u].rs].dist + 1;
    return u;
}
void dfs(ll x, ll fa) {
    for (ll i = head[x]; i; i = edge[i].pre) {
        ll y = edge[i].to;
        if (y == fa) continue;
        dfs(y, x);
        rt[x] = Merge(rt[x], rt[y]);
        sum[x] += sum[y];
        sz[x] += sz[y];
    }
    while (sum[x] > m && sz[x]) {
        sum[x] -= tr[rt[x]].val;
        rt[x] = Merge(tr[rt[x]].ls, tr[rt[x]].rs);
        sz[x]--;
    }
    ans = max(ans, sz[x] * w[x]);
}
int main() {
    cin >> n >> m;
    for (ll i = 1; i <= n; i++) {
        ll b;
        cin >> b >> c[i] >> w[i];
        if (b != 0) {
            add(b, i);
            add(i, b);
        }
        rt[i] = i;
        tr[i].ls = tr[i].rs = tr[i].dist = 0;
        tr[i].val = sum[i] = c[i];
        sz[i] = 1;
    }
    dfs(1, 0);
    cout << ans;
    return 0;
}

 

派遣「APIO2012」(左偏树)

上一篇:C#中的Lambda表达式


下一篇:C#使用MD5加密