https://www.luogu.com.cn/problem/P1552
线段树合并
题意就是,在一棵树中,每个点有一个花费,还有一个权值,需要找出一堆点,使得它们的花费总和小于等于 (m),且全都在同一子树中,最大化点的个数乘以这个子树的根的权值
子树的根可以不选
枚举子树的根,肯定是选子树中前 (k) 小花费的点,可以用类似与前缀和的二分来做
但是如果只是前缀和,那么没做一次就都得 (O(n)),因为无法通过儿子的前缀和加上其他某些信息来推知父亲的前缀和
所以要用到线段树,以花费为下标键动态开点线段树,从线段树上二分,并从儿子往父亲合并
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN puts("")
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
#define N 100006
int n,m;
struct Node{
Node *ls,*rs;
long long sum;
int cnt;
}dizhi[N*80],*root[N],*null=&dizhi[0];
int tot;
int fa[N],L[N],C[N];
inline void New(Node *&a){
a=&dizhi[++tot];a->ls=a->rs=null;
}
void change(Node *tree,int l,int r,int pos){
tree->sum+=pos;tree->cnt++;
if(l==r) return;
int mid=(l+r)>>1;
if(pos<=mid){
if(tree->ls==null) New(tree->ls);
change(tree->ls,l,mid,pos);
}
else{
if(tree->rs==null) New(tree->rs);
change(tree->rs,mid+1,r,pos);
}
}
Node *merge(Node *a,Node *b,int l,int r){
if(a==null) return b;
if(b==null) return a;
if(l==r){
a->sum+=b->sum;a->cnt+=b->cnt;
return a;
}
int mid=(l+r)>>1;
a->ls=merge(a->ls,b->ls,l,mid);
a->rs=merge(a->rs,b->rs,mid+1,r);
a->sum=a->ls->sum+a->rs->sum;
a->cnt=a->ls->cnt+a->rs->cnt;
return a;
}
int ask(Node *tree,int l,int r,int lim){
if(tree==null) return 0;
if(l==r) return lim>=tree->sum;
int mid=(l+r)>>1;
if(lim>=tree->ls->sum) return tree->ls->cnt+ask(tree->rs,mid+1,r,lim-tree->ls->sum);
return ask(tree->ls,l,mid,lim);
}
int main(){
n=read();m=read();
for(reg int i=1;i<=n;i++){
fa[i]=read();C[i]=read();L[i]=read();
New(root[i]);
change(root[i],1,1e9,C[i]);
}
long long ans=0;
for(reg int i=n;i;i--){//题目中明确了一个点的父亲编号比他小
ans=std::max(ans,(long long)ask(root[i],1,1e9,m)*L[i]);
if(fa[i]) merge(root[fa[i]],root[i],1,1e9);
}
printf("%lld",ans);
return 0;
}