洛谷P4556 雨天的尾巴(线段树合并)

本文介绍了洛谷P4556雨天的尾巴问题,涉及一棵带有节点的树和多次操作。每个操作在指定路径上放置一定类型的救济粮,求各节点最多存放的救济粮类型。解决方案利用线段树合并,通过树上差分优化操作,最终通过深度优先搜索更新答案。时间复杂度为O(nlogn)。
摘要由CSDN通过智能技术生成

洛谷P4556 雨天的尾巴

题目大意

给你一棵有 n n n个节点的树和 m m m次操作,每次操作有三个数 x , y , z x,y,z x,y,z,表示在 x x x y y y的路径上的每个点放一袋 z z z类型的救济粮。求最后各个点中存放的最多的是哪种救济粮。

1 ≤ n , m ≤ 1 0 5 , 1 ≤ a , b , x , y ≤ n , 1 ≤ z ≤ 1 0 5 1\leq n,m\leq 10^5,1\leq a,b,x,y\leq n,1\leq z\leq 10^5 1n,m105,1a,b,x,yn,1z105

题解

前置知识:线段树合并

首先,对于每个点,建一棵权值线段树,维护该点所有的救济粮的最大值的类型和最大值,位置 k k k存储该节点中第 k k k种救济粮的数量。

对于每次操作,我们可以用树上差分来将其优化到 O ( l o g n ) O(logn) O(logn),具体方法如下:

  • 将节点 x , y x,y x,y的线段树中的位置 z z z加一
  • x , y x,y x,y L C A LCA LCA t t t,将 t t t t t t的父亲的线段树的位置 z z z减一
  • 最后 d f s dfs dfs一次,将所有点的子树与点合并,合并后这个点的线段树维护的就是答案

时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

code

#include<bits/stdc++.h>
#define N 100000
using namespace std;
int n,m,tot,d[200005],l[200005],r[200005],rt[200005],dep[200005],ans[100005],f[100005][20];
struct node{
    int lc,rc,t,mx;
}tr[5000005];
void add(int xx,int yy){
    l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void dfs1(int u,int fa){
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=19;i++){
        f[u][i]=f[f[u][i-1]][i-1];
    }
    for(int i=r[u];i;i=l[i]){
        if(d[i]==fa) continue;
        dfs1(d[i],u);
    }
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=19;i>=0;i--){
        if(dep[f[x][i]]>=dep[y]) x=f[x][i];
    }
    if(x==y) return x;
    for(int i=19;i>=0;i--){
        if(f[x][i]!=f[y][i]){
            x=f[x][i];y=f[y][i];
        }
    }
    return f[x][0];
}
void up(int k){
    if(tr[tr[k].lc].mx>=tr[tr[k].rc].mx){
        tr[k].mx=tr[tr[k].lc].mx;
        tr[k].t=tr[tr[k].lc].t;
    }
    else if(tr[tr[k].lc].mx<tr[tr[k].rc].mx){
        tr[k].mx=tr[tr[k].rc].mx;
        tr[k].t=tr[tr[k].rc].t;
    }
}
void pt(int &k,int l,int r,int z,int a){
    if(!k) k=++tot;
    if(l==r){
        tr[k].mx+=a;tr[k].t=z;return;
    }
    int mid=(l+r)/2;
    if(z<=mid) pt(tr[k].lc,l,mid,z,a);
    else pt(tr[k].rc,mid+1,r,z,a);
    up(k);
}
void merge(int &r1,int r2,int l,int r){
    if(!r1||!r2){
        r1=r1+r2;return;
    }
    if(l==r){
        tr[r1].mx+=tr[r2].mx;return;
    }
    int mid=(l+r)/2;
    merge(tr[r1].lc,tr[r2].lc,l,mid);
    merge(tr[r1].rc,tr[r2].rc,mid+1,r);
    up(r1);
}
void dfs2(int u,int fa){
    for(int i=r[u];i;i=l[i]){
        if(d[i]==fa) continue;
        dfs2(d[i],u);
        merge(rt[u],rt[d[i]],1,N);
    }
    if(tr[rt[u]].mx>0) ans[u]=tr[rt[u]].t;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,x,y;i<n;i++){
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    tot=0;
    dfs1(1,0);
    for(int i=1,x,y,z,LCA;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        pt(rt[x],1,N,z,1);
        pt(rt[y],1,N,z,1);
        LCA=lca(x,y);
        pt(rt[LCA],1,N,z,-1);
        pt(rt[f[LCA][0]],1,N,z,-1);
    }
    dfs2(1,0);
    for(int i=1;i<=n;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值