BZOJ 2809 (APIO 2012) 左偏树

基本算得上左偏树的模板题。
(我的逗B)思路 : 想到每个忍者都可以被自己选为领导者,这样一来就只能选他的下属以及他服务客人,因为所给的树结构是以编号为关键字排序的,又因为选取一个领导者时,他的下属的顺序是不影响到最终客人的满意度的,所以我们dfs枚举每个人做领导者,以他们的花费为关键字写一个小根堆,(小根堆中的元素就是原本给出的树中被选做领导者的那颗子树),其中根节点是例外的,有可能不是最小值。由于是递归的,计算一个节点的最大满意度时首先要把他的所有子节点进行合并,又因为根节点是例外的,在每层递归结束时要维护小根堆性质,所以我们合并的并不是在原来的数里它的下属,所以这就又需要dfs函数返回一个值,代表它的“新下属”,一个小根堆的堆顶。注意最开始给定的树和我们写的左偏树是互不相干的。这样,每一次递归,我们需要计算的是一个最大满意度,求一个max,我们有的条件就是一个小根堆。因为我们枚举每个为根节点就在复杂度里乘了一个n,所以就要求在每次递归时,需要快速的从一个小根堆中求出最多可以选出多少忍者。由于枚举时是强制枚举对象为树根的,它可能不满足小根堆,所以会麻烦一点。 (这是我最开始想到的思路, 发现写到这里就会挂掉,于是灰心地去看了下PPT中的提示)

(可以A掉的)思路:仍然是dfs枚举每个人为领导者,与我的思路不同的是维护的是一个大根堆,多记录一些信息,是堆里的花费总和以及节点数。这样一来,每次枚举的过程中,只要花费总和大于了上限,就删除堆顶,而这个被删除的堆顶,在递归回溯的过程中是不会再被用到的,因为在回溯过程中,这棵树是被作为一颗子树使用的,而它自身已经达到花费上限了,所以肯定还要删除,继续删除堆顶。需要注意的是,dfs的过程是要返回树根的,因为比如dfs(x),而x在之后的过程中被删除或者不是最大,都不会成为堆顶,而合并的是堆顶,所以dfs需要返回堆顶编号,这样也就解决了我的逗B思路中的“小根堆的堆顶可能例外”的情况。

略兴奋,虽然借鉴了别人的思路和通用的模板(自己写的模板在上一篇博客中有的,后来给删了,因为它会挂掉),不过初次在BZOJ上交题一遍A,现在还有点小激动,下面是代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

int n, m;
unsigned long long ans;
vector <int> sub[100005];

struct node{
    int led, rew, lc, rc, h;
    unsigned long long sum, size;
}t[100005];

void init(){
    int mas, rew, led;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%d %d %d", &mas, &rew, &led);
        t[i].rew = rew;
        t[i].led = led;
        t[i].size = 1;
        t[i].sum = rew;
        sub[mas].push_back(i);
    }
}

int merge(int A, int B){
    if(!A) return B;
    if(!B) return A;
    if(t[A].rew < t[B].rew) swap(A, B);
    t[A].rc = merge(t[A].rc, B);
    int lc = t[A].lc, rc = t[A].rc;
    t[A].sum = t[lc].sum + t[rc].sum + t[A].rew;
    t[A].size = t[lc].size + t[rc].size + 1; 
    if(t[t[A].lc].h < t[t[A].rc].h) swap(t[A].lc, t[A].rc);
    if(t[A].rc) t[A].h = t[A].rc+1;
    else t[A].h = 0;
    return A;
} 

int dfs(int x){
    int root = x;
    for(int i = 0; i < sub[x].size(); i++){
        int c = sub[x][i];
        root = merge(root, dfs(c));
    }
    while(t[root].sum > m)
        root = merge(t[root].lc, t[root].rc);
    ans = max(ans, t[root].size * t[x].led);
    return root;
}

int main()
{
    init();
    dfs(1);
    printf("%lld", ans);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值