【刷题】【数据结构】【堆】配对堆 [APIO2012] 派遣

这一题中各位忍者的关系其实是一棵树:

b[i]是他的上司,也是他的父节点

一个父节点可能有多个儿子

父子关系就是命令的传达关系,父亲能传给儿子

所以如果选中一个管理者,那他就是这棵树的根

因为忍者只要祖宗是管理者,就可以传话,

所以管理者可以选择的忍者的范围就是他的所有子孙

而传话的忍者也可以不被调遣,管理者也可以

所以选择忍者时,不用要求这个被选择点的父亲也被选择

那么把所有点放到一个堆中,然后按照w的值重新开始排序,合并

找最大值删除就好

//思路:其实这道题很保留,枚举管理者,枚举被派遣的人
//但是,是有方法的枚举
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<queue>
using namespace std;
int n,m;
long long ans;
const int N=100003;
vector <int > son[N];
int b[N],w[N],val[N];

int fa[N];
int find(int x) { return fa[x]==0?x:fa[x]=find(fa[x]); }
int merge(int a,int b)//这里是w的最大堆 
{
    if(a==b) return a;
    if(w[a]<=w[b]) return fa[a]=b,son[b].push_back(a), b;
    else return fa[b]=a,son[a].push_back(b), a; 
}
int del(int x)
{
    queue <int> s; 
    int siz=son[x].size() ;
    for(int i=0;i<siz;i++)
    {
        int t=son[x][i];
        fa[t]=0;
        s.push(t);
    }
    while((siz--)>1)
    {
        int a=s.front();s.pop();
        int b=s.front();s.pop();
        s.push(merge(a,b));
    }
    if(!s.empty() ) fa[x]=s.front();
    son[x].clear() ;
    return w[x];
}

int sz[N];
long long sum[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&b[i],&w[i],&val[i]);
        ans=max((long long)val[i],ans);//1
        sz[i]=1,sum[i]=w[i];
    }
    for(int i=n;i;i--)//每一个忍者的老板的编号一定小于自己的编号Bi<i
    //这连欧拉序都省了, 直接从n(就是底部)开始遍历,往上走 
    {
        int f=b[i];//这就是根
        sz[f]+=sz[i],sum[f]+=sum[i];
        merge(find(i),find(f));//!!!!!!!这里不可以直接写f,
        //因为f有不止一个儿子,在之前已经被拉出来merge过了,find(f)不一定等于f 
        while(sum[f]>m)
        {
            sum[f]-=del(find(f));
            sz[f]--;
        }
        ans=max(ans,sz[f]*1LL*val[f] );
    }
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/xwww666666/p/11260755.html