• [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;
    }
    
  • 相关阅读:
    算法作业10——0-1装载问题(背包问题)
    算法作业9-1——最长公共子序列问题
    算法作业9-2——背包问题
    算法作业7——投资问题
    算法作业6——选第k小的元素:特定分治策略
    【Java并发编程】并发编程大合集
    Redis学习笔记-Redis内部数据结构
    近期的随笔
    2013年的总结,比以往时候来得晚了一些
    探索推荐引擎内部的秘密,第 3 部分: 深入推荐引擎相关算法
  • 原文地址:https://www.cnblogs.com/HLXZZ/p/7638227.html
Copyright © 2020-2023  润新知