[bzoj2809] 派遣

题意:给你一棵树,n个结点,每个点有两个权值\(a_i,b_i\),你可以选择一个结点,然后在这个结点的子树内选择一些结点,并且这些结点的\(\sum{a_i}\)小于m,则该点的贡献为\(b_i*所选结点的个数\),求最大的贡献

题解:

可并堆(斜堆)

从根dfs,维护一个当前子树的大根堆,如果子树和大于m,就一直pop权值最大的点,然后用斜堆合并即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define N 100010
using namespace std;

int n,m,tot,e_num,nxt[N],to[N],h[N],ls[N],rs[N],rt[N],a[N],b[N],v[N];
ll ans,sum[N],siz[N];

int gi() {
  int x=0,o=1; char ch=getchar();
  while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
  if(ch=='-') o=-1,ch=getchar();
  while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
  return o*x;
}

void add(int x, int y) {
  nxt[++e_num]=h[x],to[e_num]=y,h[x]=e_num;
}

int merge(int x, int y) {
  if(!x || !y) return x+y;
  if(v[x]<v[y]) swap(x,y);
  rs[x]=merge(rs[x],y);
  swap(ls[x],rs[x]);//swap是为了尽可能让树平衡
  return x;
}

void dfs(int u) {
  rt[u]=++tot,v[rt[u]]=a[u];//按照dfs序建堆,给树重新编号
  sum[u]=a[u],siz[u]=1;
  for(int i=h[u]; i; i=nxt[i]) {
    int v=to[i];
    dfs(v);
    sum[u]+=sum[v],siz[u]+=siz[v];
    rt[u]=merge(rt[u],rt[v]);//注意1
  }
  while(sum[u]>m) {
    sum[u]-=v[rt[u]];
    rt[u]=merge(ls[rt[u]],rs[rt[u]]);//注意2
    siz[u]--;
  }
  ans=max(ans,siz[u]*b[u]);
}

int main() {//注意:所有有关堆的操作,一定是在新的编号上进行的,前期不熟练,建议把各种操作写成函数
  n=gi(),m=gi();
  for(int i=1; i<=n; i++) {
    int x=gi();
    a[i]=gi(),b[i]=gi();
    add(x,i);
  }
  dfs(1);
  printf("%lld", ans);
  return 0;
}
posted @ 2017-10-08 19:13  HLX_Y  阅读(170)  评论(0编辑  收藏  举报