luogu P1552 [APIO2012]派遣 题解--可并堆/贪心

  • 题目链接:

    https://www.luogu.org/problemnew/show/P1552

  • 分析:

    一开始愣是没看懂题,后面发现就是你要找一个树上点集使得各点权值之和小于\(M\),并且找一个点集的公共祖先\(Anc\)(管理者),使\(Anc\)的领导力乘以点集大小最大

    一开始想DP,一看数据范围,我们可以稍微暴力一点,枚举每个点作为管理者的答案最大值,我们只要在子树中权值最小的点选起使权值之和小于\(M\)就可以了,一下问题简单了许多.

    再暗中观察分析,发现这个信息是可以维护转移的,在两颗子树合并时可以用大根堆搞一搞.我们不妨先对树进行\(dfs\),中途不断合并权值信息.

    \(dfs\) \(x\)节点时,以\(x\)为根建一个大根堆称为x的代表堆,\(dfs\)\(x\)的一个子树回溯到\(x\)时,将子树的代表堆与\(x\)的代表堆进行合并,同时将两堆权值之和相加.若权值之和大于\(m\),由于是大根堆,不断\(pop\)堆顶直至小于\(m\),满足最优子结构.

  • 注意

    本题思维容易但要注意一些问题:

    1. 虽说是X的代表堆,然而X可能会被\(pop\)这个堆,于是要用一个\(root\)记录代表堆的根,同时dfs返回该root值

    2. 可并堆可用斜堆或左偏堆 COGS上斜堆略胜一筹,luogu上却不尽人意

    3. 还有一些细节和小技巧,比如处理祖宗的最大领导力可以看代码

  • 代码(左偏树)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <map>
#include <queue>
#define ri register int 
#define ll long long 
using namespace std;
const int maxn=103005;
const int inf=0x7fffffff;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c=='-';
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;
    return ;
}
int n;
ll  m,leader[maxn],key[maxn],ans[maxn],anss=-inf;
int ls[maxn],rs[maxn],fa[maxn],cnt[maxn],npl[maxn];//可并堆
vector <int>son[maxn];
int get(int x){
    while(x!=fa[x])fa[x]=get(fa[x]);
    return fa[x];
}
int merge(int x,int y){
    if(!x)return y;
    if(!y)return x;
    if(key[x]<key[y])swap(x,y);
    fa[y]=x;
    rs[x]=merge(rs[x],y);
    if(!ls[x]||npl[ls[x]]<npl[rs[x]])swap(ls[x],rs[x]);
    if(!rs[x])npl[x]=0;
    else npl[x]=npl[rs[x]]+1;
    //swap(ls[x],rs[x]);
    return x;
}
int del(int now){
    fa[now]=-1;
    fa[ls[now]]=fa[rs[now]]=0;
    return merge(ls[now],rs[now]);
}
int dfs(int now,ll mx){//mx--祖先上的领导力最大值
    int s,root=now,ro_s;
    mx=max(mx,leader[now]);
    ans[now]=key[now],cnt[now]=1;
    anss=max(anss,leader[now]);
    for(ri i=0;i<son[now].size();i++){      
        s=son[now][i];      
        ro_s=dfs(s,mx); 
        ans[now]+=ans[s],cnt[now]+=cnt[s];
        root=merge(root,ro_s);  
        while(ans[now]>m&&cnt[now]) {
            ans[now]-=key[root];
            cnt[now]--;
            root=del(root);
        }
        anss=max(anss,cnt[now]*mx);
    }
    return root;
}
int main(){
    int pa;
    //freopen("dispatching.in","r",stdin);
    //freopen("dispatching.out","w",stdout);
    read(n),read(m);
    for(ri i=1;i<=n;i++){
        read(pa),read(key[i]),read(leader[i]);
        ls[i]=rs[i]=0,fa[i]=i,npl[i]=0;
        son[pa].push_back(i);
    }
    dfs(1,leader[1]);
    printf("%lld\n",anss);
    fclose(stdin);
    fclose(stdout);
    return  0;
}

转载于:https://www.cnblogs.com/Rye-Catcher/p/9260888.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值