[dfs序+主席树] BZOJ2809: [Apio2012]dispatching

题意

在一个忍者的帮派里,一些忍者们将被选中派遣给顾客。
在这个帮派里,有一名忍者被称之为 Master。除了 Master以外,每名忍者都有且仅有一个上级。
所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送。
现在你要招募一批忍者,并把它们派遣给顾客。你需要为每个被派遣的忍者支付一定的薪水,薪水总额不超过你的预算。
另外,为了发送派遣指令,你需要选择一名忍者作为管理者,要求这个管理者可以向所有被派遣的忍者 发送指令,在发送指令时,任何忍者(不管是否被派遣)都可以作为消息的传递 人。管理者自己可以被派遣,也可以不被派遣。
你的目标是在预算内使顾客的满意度最大。这里定义顾客的满意度为派遣的忍者总数乘以管理者的领导力水平。
(n<=100000)

题解

显然构成一棵树。若某节点作为管理者,他能发送指令给以他为根的子树中的所有节点。
可以想到枚举管理者,然后想办法得到子树中的薪水信息,优先挑花费少的,求出最多的派遣的忍者总数。
如何得到子树中的信息呢?可以想到dfs序+主席树。对每个节点记下入栈和出栈的时间戳,对应的线段树减一下,这样能在值域线段树中方便的求出最多能派遣多少个了。
复杂度 O(nlog2n)
好惨啊,调了2h最后发现没开long long

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=100005, maxe=100005;
int n,m,cst[maxn],ld[maxn],Tim,dfn[maxn],lst[maxn];
int fir[maxn],nxt[maxe],son[maxe],tot;
struct node{
    int L,R,num; LL sum; node* ch[2];
    node(int x1=0,int x2=0,int x3=0,LL x4=0,node* x5=NULL){ L=x1; R=x2; num=x3; sum=x4; ch[0]=ch[1]=x5; }
    void maintain(){ sum=ch[0]->sum+ch[1]->sum; num=ch[0]->num+ch[1]->num; }
} nil, *null=&nil;
typedef node* P_node;
struct data{
    int w,id;
    bool operator < (const data &b)const{ return w<b.w; }
} b[maxn];
P_node Updata(P_node pre,int pos){
    P_node p=new node(pre->L,pre->R,pre->num,pre->sum,null); 
    p->ch[0]=pre->ch[0]; p->ch[1]=pre->ch[1];
    if(p->L==p->R){ p->num++; p->sum+=b[pos].w;  return p; }
    int mid=(p->L+p->R)>>1;
    if(pos<=mid) p->ch[0]=Updata(p->ch[0],pos);
            else p->ch[1]=Updata(p->ch[1],pos);
    p->maintain(); return p;
}
int Query(P_node p1,P_node p2,int sval){
    if(p1==null) return 0;
    if(p1->sum-p2->sum<=sval) return p1->num-p2->num; 
    LL suml=p1->ch[0]->sum-p2->ch[0]->sum, numl=p1->ch[0]->num-p2->ch[0]->num;
    if(suml>=sval) return Query(p1->ch[0],p2->ch[0],sval);
    return numl+Query(p1->ch[1],p2->ch[1],sval-suml);
}
P_node build(int L,int R){
    P_node p=new node(L,R,0,0,null);
    if(L==R) return p;
    int mid=(L+R)>>1;
    p->ch[0]=build(L,mid); p->ch[1]=build(mid+1,R);
    p->maintain(); return p;
}
//----------------------------------------------------------------------------------
void add(int x,int y){
    son[++tot]=y; nxt[tot]=fir[x]; fir[x]=tot;  
}
P_node rt[maxn];
int id[maxn];
void dfs_que(int x){
    dfn[x]=++Tim; rt[Tim]=Updata(rt[Tim-1],id[x]);
    for(int j=fir[x];j;j=nxt[j]) dfs_que(son[j]);
    lst[x]=Tim;
}
LL ans;
int main(){
    freopen("bzoj2809.in","r",stdin);
    freopen("bzoj2809.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int x; scanf("%d%d%d",&x,&cst[i],&ld[i]); b[i].w=cst[i]; b[i].id=i;
        if(x) add(x,i);
    }
    sort(b+1,b+1+n); for(int i=1;i<=n;i++) id[b[i].id]=i;
    rt[0]=build(1,n); 
    dfs_que(1);
    for(int i=1;i<=n;i++){
        LL res=Query(rt[lst[i]],rt[dfn[i]-1],m);
        ans=max(ans,res*ld[i]);
        //printf("%d: %lld\n",i,res);
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值