线段树的分裂和合并(加强版)

线段树的分裂和合并(加强版)

上一篇文章讲了线段树分裂和合并的模板题,下面加强一下理解

题目链接:https://www.luogu.com.cn/problem/P4556

深绘里一直很讨厌雨天。

灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。

虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。

无奈的深绘里和村民们只好等待救济粮来维生。

不过救济粮的发放方式很特别。

题目描述

首先村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 m 次发放,每次选择两个房屋 (x, y),然后对于 x 到 y 的路径上(含 x 和 y)每座房子里发放一袋 z 类型的救济粮。

然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

输入格式

输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 n 和救济粮发放的次数 m。

第 2 到 第 n 行,每行有两个用空格隔开的整数 a, b,代表存在一条连接房屋 a 和 b 的边。

第 (n + 1) 到第 (n+m) 行,每行有三个用空格隔开的整数 x, y, z,代表一次救济粮的发放是从 x 到 y 路径上的每栋房子发放了一袋 z 类型的救济粮。

输出格式

输出 n 行,每行一个整数,第 i 行的整数代表 ii 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。

如果某座房屋没有救济粮,则输出 0。

这一道题主要算法有最近公共祖先,线段树合并,其次还有树上差分的思想

主要题意:有一个树形的村庄,每次操作会向x到y的路径的每一户村民发放一袋种类为z的救济粮,最后要求每一户村民家中的数量最多的救济粮的编号,如果数量相同,输出编号较小的

做法:向x到y路径的上每一户村民发放救济粮,可以利用树上差分,给x和y节点的线段树的z救济粮数量加一,x和y的最近公共祖先的线段树的救济粮减1,最近公共祖先的父节点减1,

最后dfs一遍,回溯的时候将子节点的线段树合并到父节点的线段树上去,维护区间最大值即可

那么为什么可以这样做呢,最后可以达到将x到y路径上的每一个点都加上1的效果。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 100010, M = N * 2;
int e[M], ne[M], h[N], idx;
int top[N], f[N], son[N], depth[N], sz[N];
int n, m, timestamp, tot;
int root[N], ans[N];

struct Node{
    int l, r;
    PII val;
}tr[N * 70];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void dfs1(int u, int fa)
{
    depth[u] = depth[fa] + 1, f[u] = fa, sz[u] = 1;
    int mxsz = 0;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == fa) continue;
        dfs1(j, u);
        sz[u] += sz[j];
        if(mxsz < sz[j]) 
        {
            mxsz = sz[j];
            son[u] = j;
        }
    }
}

void dfs2(int u, int  t)
{
    top[u] = t;
    if(son[u]) dfs2(son[u], t);
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == f[u] || j == son[u]) continue;
        dfs2(j, j);
    }
}

int lca(int x,int y) //这里我用的是树链刨分求lca,当然也可以用倍增
{
    while(top[x] != top[y])
    {
        if(depth[top[x]] < depth[top[y]]) swap(x, y);
        x = f[top[x]];
    }
    return depth[x] < depth[y] ? x : y;
}

PII max(PII& x, PII& y) 
{
    if(x.first > y.first) return x;
    else if(x.first == y.first) return x.second < y.second ? x : y;
    else return y;
}

inline void pushup(int k) { tr[k].val = max(tr[tr[k].l].val, tr[tr[k].r].val); }

void modify(int &k, int l,int r,int p,int x)
{
    if(!k) k= ++ tot;
    if(l==r) 
    {
        tr[k].val.first += x; 
        tr[k].val.second = p; 
        return;
    }
    int m = (l+r)>>1;
    if(p<=m) modify(tr[k].l, l, m, p, x);
    else modify(tr[k].r, m+1, r, p, x);
    pushup(k);
}

void merge(int &x,int y,int l=1,int r=N) 
{
    if(!(x&&y))
        x|=y;
    else if(l==r) 
        tr[x].val.first += tr[y].val.first;
    else
    {
        int m = (l+r)>>1;
        merge(tr[x].l, tr[y].l, l, m);
        merge(tr[x].r, tr[y].r, m+1, r);
        pushup(x);
    }
}

void dfs(int u)
{
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == f[u]) continue;
        dfs(j);
        merge(root[u], root[j]);
    }
    if(tr[root[u]].val.first) ans[u] = tr[root[u]].val.second;
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for(int i = 1; i < n; i ++ )
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    
    dfs1(1, 0);
    dfs2(1, 0);
    
    while(m -- )
    {
        int x, y, z;
        cin >> x >> y >> z;
        int anc = lca(x, y);
        modify(root[x], 1, N, z, 1);
        modify(root[y], 1, N, z, 1);
        modify(root[anc], 1, N, z, -1);
        modify(root[f[anc]], 1, N, z, -1);
    }
    
    dfs(1);
    
    for(int i = 1; i <= n; i ++ ) cout << ans[i] << endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奥利给~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值