jzoj 5833. Endless Fantasy(树上启发式合并)

本文介绍了一种解决树形结构中子树最大部落问题的算法——树上启发式合并。该方法通过先处理轻节点再处理重节点,并使用桶维护各节点信息,实现了高效的子树合并。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:

给你一个树,每个节点上有有一个部落,以及部落的人数,要你求出每个节点的子树里面人数最多的部落是哪一个(人数相同部落编号最小的)。

思路:

树上启发式合并,每次先做轻节点,后做重节点,然后开一个桶维护,轻节点的桶做完以后清理,重节点的桶不用清理,直接传给父亲。

程序:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 400005
struct data{int to,next;}e[N*2];
struct data1{int a,b;}ans[N];
struct ms{int a,b;}ms[N];
int last[N],size[N],a[N],b[N],t[N];
int n,m,cnt,u,v;
void add(int x,int y){
    e[++cnt].to=y; e[cnt].next=last[x]; last[x]=cnt;
    e[++cnt].to=x; e[cnt].next=last[y]; last[y]=cnt;
}

void dfs(int x,int fa){
    size[x]=1;
    for (int i=last[x];i;i=e[i].next)
    if (e[i].to!=fa){
        dfs(e[i].to,x);
        size[x]+=size[e[i].to];
        if (size[e[i].to]>ms[x].a) ms[x].a=size[e[i].to],ms[x].b=e[i].to;
    }
}

void jia(int x,int fa,int la){
    t[b[x]]+=a[x];
    if (t[b[x]]==ans[la].a) ans[la].b=std::min(b[x],ans[la].b);
    if (t[b[x]]>ans[la].a) ans[la].a=t[b[x]],ans[la].b=b[x];
    for (int i=last[x];i;i=e[i].next)
    if (fa!=e[i].to) jia(e[i].to,x,la);
}

void clean(int x,int fa){
    t[b[x]]-=a[x];
    for (int i=last[x];i;i=e[i].next)
    if (fa!=e[i].to) clean(e[i].to,x);
}

void solve(int x,int fa){
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa&&e[i].to!=ms[x].b) solve(e[i].to,x);
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa&&e[i].to==ms[x].b) solve(e[i].to,x);
    ans[x].a=ans[ms[x].b].a;
    ans[x].b=ans[ms[x].b].b;
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa&&e[i].to!=ms[x].b) jia(e[i].to,x,x);
    t[b[x]]+=a[x];
    if (t[b[x]]==ans[x].a) ans[x].b=std::min(b[x],ans[x].b);
    if (t[b[x]]>ans[x].a) ans[x].a=t[b[x]],ans[x].b=b[x];
    if (ms[fa].b!=x) clean(x,fa);
}

int main(){
    freopen("endless.in","r",stdin);
    freopen("endless.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    for (int i=1;i<=n;i++)  scanf("%d%d",&b[i],&a[i]);
    dfs(1,0);
    solve(1,0);
    for (int i=1;i<=n;i++) printf("%d %d\n",ans[i].b,ans[i].a);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值