题意
选择一个节点x,并在其子树中选num个节点,它们的薪水不能超过m,满意度为x.l*num,求最大满意度。
特性
在一棵子树中,选的节点越多越好,所以要选薪水尽量小的节点。
尝试
这题很满足可并堆,自底向上,建许多的小根堆,每个节点先把所有子节点的小根堆给并起来,那么每次的决策就是一直取堆顶直到不能再取,记下num就好了。
但是这么做时间复杂度太大了。
题解
贪心+可并堆
小的不行就用大的嘛!
建立大根堆,当堆中薪水和大于m时,首选踢出堆顶,直到小于等于m。同时维护堆中元素个数,这样就可以很轻松的接替小根堆的工作了。
在合并时,也不必考虑那么多,子节点的小根堆都是尽量大的了,也是在子节点中最优的了。把它们合并起来时,原先已被踢出的点显然没有价值,这样每个点只会被踢出一次,大大降低了时间复杂度。
代码
这题不需要记录fa了,因为它是一棵树,合并流程十分清晰,可以直接得到fa。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=100010;
int n,m;
int l[MAXN];
struct E{int y,next;}e[MAXN];int len=0,last[MAXN];
void ins(int x,int y)
{
e[++len]=(E){y,last[x]};last[x]=len;
}
int rt[MAXN],ch[MAXN][2];
ll sum[MAXN];
int a[MAXN],dis[MAXN],num[MAXN];
int merge(int x,int y)
{
if(!x || !y) return x+y;
if(a[x]<a[y]) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
int pop(int x)
{
return merge(ch[x][0],ch[x][1]);
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
rt[i]=i;num[i]=1;=
int x,y=i;
scanf("%d %d %d",&x,&a[i],&l[i]);sum[i]=a[i];
ins(x,y);
}
ll ans=0;
for(int x=n;x>=1;x--)
{
for(int k=last[x];k;k=e[k].next)
{
int y=e[k].y;
rt[x]=merge(rt[x],rt[y]);
sum[x]+=sum[y];num[x]+=num[y];
}
while(num[x] && sum[x]>m)
{
sum[x]-=a[rt[x]];num[x]--;
rt[x]=pop(rt[x]);//在pop后要记录新的rt
}
ans=max(ans,(ll)num[x]*l[x]);
}
printf("%lld\n",ans);
return 0;
}