[APIO2012]派遣 (树上线段树合并+倍增)

 

       题面戳这里

       大概题意就是,所有忍者有两个属性值分别为薪水值c和领导力值v,有一个预算m,要求找到一位忍者当领导,领导力值为V,并在他的子树里选尽可能多的忍者(他自己可选可不选),数量设为cnt,使得   ,要使ans=cnt*V最大。

       显然的一件事是,当领导确定时,在他子树里最优的取法一定是取c值前cnt小的手下,所以我们想到对于每个节点开一条权值线段树的链,然后从叶子一路合并上去,每到一个节点就在此节点维护的线段树上二分来找到符合条件的最大的cnt。

       然而有一个特殊的情况需要注意,就是当二分到权值线段树的叶子节点时发现叶子节点储存的cnt> 1,全取会使\sum c> m。这时就需要把cnt二进制拆分一下,用类似倍增的方法找到这个叶子节点到底取多少个,这样才能保证是最优的。由于是到二分叶子节点只会倍增一次,所以每次查询复杂度还是O(\log n)的,总体复杂度O(n\log n)

      最后附上本蒟蒻一份AC代码:

#include<cstdio>//CindyMarshall
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define N 100005
#define ll long long
#define lc (ls[x])
#define rc (rs[x])
int n,m,h[N],nxt[N],to[N],tre,b[N],mx,tot,root;
struct la{int c,v;}w[N];
inline void tu(int x,int y){to[++tre]=y,nxt[tre]=h[x],h[x]=tre;}
int tr[N*20],rt[N],ls[N*20],rs[N*20];
ll sum[N*20],ans;
void ud(int x){
    tr[x]=tr[lc]+tr[rc],sum[x]=sum[lc]+sum[rc];
}
void bt(int l,int r,int p,int &x){
    if(l>r)return;
    if(!x)x=++tot;
    if(l==r&&l==p){tr[x]=1,sum[x]=b[l];return;}
    int mid=(l+r)>>1;
    if(p<=mid)bt(l,mid,p,lc);
    else bt(mid+1,r,p,rc);
    ud(x);
}
int merge(int l,int r,int x,int y){
    if(!x)return y;if(!y)return x;
    if(l==r){tr[x]+=tr[y],sum[x]+=sum[y];return x;}
    int mid=(l+r)>>1;
    ls[x]=merge(l,mid,ls[x],ls[y]);
    rs[x]=merge(mid+1,r,rs[x],rs[y]);
    ud(x);
    return x;
}
int erfen(int l,int r,int x,ll now,int cnt){
    if(l==r){
        if(now+sum[x]<=m)cnt+=tr[x];
        else{ll tmp=now;
            for(int j=log2(tr[x]);j>=0;--j){
                if((1<<j)*b[l]+tmp<=m)cnt+=(1<<j),tmp+=(1<<j)*b[l];
            }
        }
        return cnt;
    }
    int mid=(l+r)>>1;
    if(now+sum[lc]>=m)return erfen(l,mid,lc,now,cnt);
    else return erfen(mid+1,r,rc,now+sum[lc],cnt+tr[lc]);
}
void dfs(int u){
    for(int i=h[u];i;i=nxt[i]){
        dfs(to[i]);
        rt[u]=merge(1,mx,rt[u],rt[to[i]]);
    }
    ans=max(ans,1ll*w[u].v*erfen(1,mx,rt[u],0,0));
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,d;i<=n;++i){scanf("%d%d%d",&d,&w[i].c,&w[i].v);tu(d,i);b[i]=w[i].c;}
    sort(b+1,b+n+1);
    mx=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;++i)w[i].c=lower_bound(b+1,b+mx+1,w[i].c)-b;
    for(int i=1;i<=n;++i)bt(1,mx,w[i].c,rt[i]);
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值