【APIO2012T1】派遣-贪心+左偏树
测试地址:派遣
做法:我们知道,对于每一棵子树,我们都贪心选里面费用最小的一些点,一直选直到如果再选费用就超限为止,这时以这棵子树的根为管理者的最优解显然是根的领导力乘上选的点数。但是,如果对于每棵子树都排序选择的话,时间复杂度O(N^2*logN),即使使用归并排序时间复杂度也达到O(N^2),不能满足要求。但我们可以换一种思路,对于每棵子树建一个大根堆,表示在这颗子树上贪心应选择的点,键值为费用,设这个集合为Q。我们知道如果在一棵子树中不选的点,在更上层的子树中也同样不会被选,所以可以把这些点直接抛弃。那么对于一个节点,先把它所有子节点的Q和当前节点合并,如果加起来的费用超过限制,则逐个删掉堆顶节点直到不超过限制为止。合并堆我们可以用左偏树O(logN)复杂度解决,每个节点的Q至多被合并一次,总复杂度就是O(NlogN),而每个点至多入堆和出堆一次,删除的总复杂度为O(NlogN),所以这个算法的复杂度为O(NlogN),可以解决这个问题。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,rt[100010],first[100010]={0},tot=0;
ll m,l[100010],ans=0;
struct leftist
{
int lc,rc,d;
ll c,s,h;
}nd[100010];
struct edge {int v,next;} e[100010];
void insert(int a,int b)
{
e[++tot].v=b,e[tot].next=first[a],first[a]=tot;
}
int find(int x)
{
int r=x,i=x,j;
while(r!=rt[r]) r=rt[r];
while(i!=r) j=rt[i],rt[i]=r,i=j;
return r;
}
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
if (nd[x].c<nd[y].c) swap(x,y);
nd[x].rc=merge(nd[x].rc,y);
int lft=nd[x].lc,rht=nd[x].rc;
rt[rht]=x;
if (nd[lft].d<nd[rht].d) swap(nd[x].lc,nd[x].rc);
nd[x].s=nd[nd[x].lc].s+nd[nd[x].rc].s+nd[x].c;
nd[x].h=nd[nd[x].lc].h+nd[nd[x].rc].h+1;
nd[x].d=nd[nd[x].rc].d+1;
return x;
}
int del(int x)
{
int lft=nd[x].lc,rht=nd[x].rc;
nd[x].lc=nd[x].rc=0;
nd[x].s=nd[x].c,nd[x].h=1;
rt[lft]=lft,rt[rht]=rht,rt[x]=x;
return merge(lft,rht);
}
void dfs(int v)
{
for(int i=first[v];i;i=e[i].next)
{
dfs(e[i].v);
rt[v]=merge(find(v),find(e[i].v));
}
while(nd[rt[v]].s>m)
{
rt[v]=del(rt[v]);
}
ans=max(ans,l[v]*nd[rt[v]].h);
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++)
{
int b;
scanf("%d%lld%lld",&b,&nd[i].c,&l[i]);
nd[i].lc=nd[i].rc=nd[i].d=0;
rt[i]=i;nd[i].s=nd[i].c;nd[i].h=1;
if (b) insert(b,i);
}
nd[0].d=-1;nd[0].s=nd[0].h=0;
dfs(1);
printf("%lld",ans);
return 0;
}