版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/89082131
题意:
给你一棵
个点的有根树,每个点有两个权值。给你一个
,你要在这
个点中选出一个点,使得从子树中任意选出若干个点(不一定要选根,也不一定要连通),这些点的第一类权值之和不超过
,要让用这些点的个数乘选出的子树的根节点的第二类权值的积最大,求这个最大乘积。
。
题解:
这个题有很多做法,显然是可以线段树合并的,但是我现在要学左偏树,听同学推荐了这个题。
这个题的话左偏树思维难度感觉还是比线段树合并稍微大一点的。主要问题是,你用左偏树的话合并的时间和空间复杂度都是对的,但是你查询要一个一个拿出来再一个一个放回去,这个过程可能会变成 的。
然而其实也不怎么难想解决方法。我们不难发现在当前时刻不可能被用到的点,再合并进来一个左偏树之后那些点还是不可能被用到。所以我们的思路是维护一个大根堆,然后记录堆中元素总和,每次删最大的,直到堆中元素之和不超过 ,顺便再记录一个堆中的元素个数就行了。
这样就做完了。复杂度 的,空间上是 的,比线段树合并优秀。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,fa[200010],c[200010],l[200010],f[200010],ch[200010][2],dis[200010];
int hed[200010],cnt,rt,sz[200010];
long long ans,sum[200010];
struct node
{
int to,next;
}a[400010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to)
{
a[++cnt].to=to;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline int getr(int x)
{
if(x==f[x])
return x;
else
{
f[x]=getr(f[x]);
return f[x];
}
}
inline int merge(int x,int y)
{
if(!x||!y)
return x+y;
if(c[x]<c[y])
swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[x]=x;
f[ch[x][0]]=x;
f[ch[x][1]]=x;
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;
}
inline int pop(int x)
{
c[x]=-1;
f[ch[x][0]]=ch[x][0];
f[ch[x][1]]=ch[x][1];
f[x]=merge(ch[x][0],ch[x][1]);
return f[x];
}
inline void dfs(int x)
{
sz[x]=1;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
dfs(y);
sz[x]+=sz[y];
int fx=getr(x),fy=getr(y);
merge(fx,fy);
fx=getr(x);
sum[x]+=sum[y];
while(sum[x]>m)
{
sum[x]-=c[getr(fx)];
sz[x]--;
fx=pop(fx);
}
}
ans=max(ans,1ll*sz[x]*l[x]);
}
int main()
{
n=read();
m=read();
for(int i=1;i<=n;++i)
{
fa[i]=read();
c[i]=read();
l[i]=read();
sum[i]=c[i];
f[i]=i;
if(fa[i])
add(fa[i],i);
else
rt=i;
}
dfs(rt);
printf("%lld\n",ans);
return 0;
}