【金牌导航】【左偏树】派遣
题目
解题思路
式子为选取节点数*l[R]
发现选取的点越多答案越大
那么每个点的贡献是一样的,这种情况下选取c[i]更小的
维护一个树顶为最大值的左偏树
从下面开始合并,如果c[i]和>m就删除树顶
左偏树
定义距离dis为当前节点去到叶子节点经过右节点的最大值
左偏树的根节点小于等于ta的子节点
左偏树的左节点距离大于等于右节点距离
左偏树支持合并操作
…
合并操作
把A,B两棵树合并,假设A中所有节点都小于B
选取A中最小的节点作为新树的根
然后合并A的右子树和B
直到有一棵树为空返回另一棵
…
删除操作
删除一个点,把ta的左右子树合并起来再挂回树上即可
注意合并后可能不满足左偏树的性质,要维护,如果左子树的距离<右子树的距离,就交换
合并后距离要向上传递
…
插入操作
可以理解为一棵只有一个点的树与左偏树合并
…
构建新树
方法1:一个点一个点暴力插入,时间复杂度为O(n logn)
方法2:仿照二叉堆的建法,把所有点放进一个先进先出的队列,每次取出两棵树合并,将合并后的树放入队列
代码
#include<iostream>
#include<cstdio>
using namespace std;
struct lzf{
int to,nxt;
}f[1000010];
struct node{
int l,r,dis,sl;
long long c,sum;
}tree[1000010];
int n,x,t,rt[1000010],head[1000010];
long long m,ans,c[1000010],li[1000010];
void add(int x,int y)
{
f[++t]=(lzf){y,head[x]};
head[x]=y;
}
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
if (tree[x].c<tree[y].c) swap(x,y);
tree[x].r=merge(tree[x].r,y);
if (tree[tree[x].l].dis<tree[tree[x].r].dis)
swap(tree[x].l,tree[x].r);
tree[x].dis=tree[tree[x].r].dis+1;
tree[x].sl=tree[tree[x].l].sl+tree[tree[x].r].sl+1;
tree[x].sum=tree[tree[x].l].sum+tree[tree[x].r].sum+tree[x].c;
return x;
}
void dfs(int x,int fa)
{
for (int i=head[x];i;i=f[i].nxt)
if (f[i].to!=fa)
{
dfs(f[i].to,x);
rt[x]=merge(rt[x],rt[f[i].to]); //与子树合并
while (tree[rt[x]].sum>m) rt[x]=merge(tree[rt[x]].l,tree[rt[x]].r); //c[i]和>m删除树顶
ans=max(ans,1ll*li[x]*tree[rt[x]].sl);
}
ans=max(ans,1ll*li[x]*tree[rt[x]].sl); //叶子节点的更新
}
int main()
{
scanf("%d%lld",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d%lld%lld",&x,&c[i],&li[i]);
add(x,i);
}
for (int i=1;i<=n;i++)
{
tree[i]=(node){0,0,-1,1,c[i],c[i]};
rt[i]=i;
if (tree[i].sum>m) rt[i]=merge(tree[i].l,tree[i].r);
}
dfs(1,0);
printf("%lld",ans);
return 0;
}