洛谷P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并

[Vani有约会] 雨天的尾巴 /【模板】线段树合并

题目背景

深绘里一直很讨厌雨天。

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

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

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

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

题目描述

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

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

输入格式

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

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

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

输出格式

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

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

样例 #1

样例输入 #1

5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3

样例输出 #1

2
3
3
0
2

提示

  • 对于 20 % 20\% 20% 的数据,保证 n , m ≤ 100 n, m \leq 100 n,m≤100。
  • 对于 50 % 50\% 50% 的数据,保证 n , m ≤ 2 × 1 0 3 n, m \leq 2 \times 10^3 n,m≤2×103。
  • 对于 100 % 100\% 100% 测试数据,保证 1 ≤ n , m ≤ 1 0 5 1 \leq n, m \leq 10^5 1≤n,m≤105, 1 ≤ a , b , x , y ≤ n 1 \leq a,b,x,y \leq n 1≤a,b,x,y≤n, 1 ≤ z ≤ 1 0 5 1 \leq z \leq 10^5 1≤z≤105。

思路分析

线段树合并在我看来其实就是树上前缀和,只不过它不在局限于点权、边权的求和,而是值域树的求和。基本上的结构是这样的:

每个节点(房屋)都对应一个值域线段树,值域线段树表征该节点存放救济粮的数量。

如果对于一条路径从起点到终点对于救济粮依次 +1 时一次操作最劣是 O ( n l o g N ) , N 为 值 域 O(nlogN),N为值域 O(nlogN),N为值域 ;很明显时间复杂度不满足条件,因此我们需要用到树上差分与前缀和来处理。

当我们在路径 2 ~ 3 发放一个 3 类型的救济粮时,我们就以树上差分的思想,在节点 2、3 的值域线段树中的 3 对应的区间 +1,在 L c a ( 2 , 3 ) 、 f a t h e r L c a ( 2 , 3 ) Lca(2,3)、{father}_{Lca(2,3)} Lca(2,3)、fatherLca(2,3) 所对应的区间 -1即可,如图。

此时单次发放救济粮的时间复杂度优化为了 O ( l o g N ) O(logN) O(logN) ,总时间复杂度为 O ( m l o g N ) O(mlogN) O(mlogN)。

当然,有差分就得有前缀和,某节点的救济粮类型对应的值域线段树应为其子树差分数组之和。

由此,我们需要引出线段树合并,当我们合并两棵线段树时,其实就是将其对应的节点权值求和并传给父节点(pushup)。

很明显,本题要输出的是每个节点对应的值域线段树中权值最大的第一个位置。

因此我们构建一个这样的结构体:

cpp 复制代码
struct Tree
{
    int l, r, sum, col;//左儿子节点、右儿子节点、权值最大值、救济粮类型
};

因此,我们的pushup函数需要给父节点的值应该是子树权值的最大值,以及对应的救济粮类型

cpp 复制代码
void pushup(int u)
{
    int l = tr[u].l, r = tr[u].r;
    if (tr[l].sum >= tr[r].sum)
    {
        tr[u].sum = tr[l].sum;
        tr[u].col = tr[l].col;
    }
    else
    {
        tr[u].sum = tr[r].sum;
        tr[u].col = tr[r].col;
    }
}

对一个权值线段树修改某个救济粮类型:

cpp 复制代码
void change(int &p, int l, int r, int typ, int k)
{
    if (!p)
        p = ++idx;
    if (l == r)
    {
        tr[p].sum += k;//修改叶子节点
        tr[p].col = typ;
        return;
    }
    int mid = l + r >> 1;
    if (typ <= mid)
        change(tr[p].l, l, mid, typ, k);
    else
        change(tr[p].r, mid + 1, r, typ, k);
    pushup(p);			//往上修改
}

对两线段树的合并:

cpp 复制代码
int merge(int a, int b, int l, int r)	//将b的值合并到a
{
    if (!a || !b)
        return a + b;//当节点a或b为空时,返回非空的节点
    if (l == r)
    {
        tr[a].sum += tr[b].sum;	//修改叶子节点
        return a;
    }
    int mid = l + r >> 1;
    tr[a].l = merge(tr[a].l, tr[b].l, l, mid);	//合并两线段树的左子树
    tr[a].r = merge(tr[a].r, tr[b].r, mid + 1, r);	//合并两线段树的右子树
    pushup(a);		//往上修改
    return a;
}

树的遍历:

cpp 复制代码
void Dfs2(int u, int fa)
{
    for (auto v : G[u])
    {
        if (v == fa)
            continue;
        Dfs2(v, u);
        root[u] = merge(root[u], root[v], 1, 100000);//将子树对应的权值线段树合并到根节点
    }
    ans[u] = tr[root[u]].sum == 0 ? 0 : tr[root[u]].col;//存储答案
}

##代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

#define all(x) x.begin(), x.end()
#define bit1(x) __builtin_popcount(x)
#define Pqueue priority_queue
#define lc p << 1
#define rc p << 1 | 1
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#define fi first
#define se second

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<ll, ll> PII;

const ll mod = 1000000007;
const ll N = 1e6 + 10;
const ld eps = 1e-9;
const ll inf = 1e18;
const ll P = 131;
const ll dir[8][2] = {1, 0, 0, 1, -1, 0, 0, -1, 1, 1, 1, -1, -1, 1, -1, -1};

struct Tree
{
    int l, r, sum, col;
} tr[N * 8];
int root[N], idx;
int f[N][21], dep[N];
int Log[N + 10];
vector<int> G[N];
int n, m, u, v, w, ans[N];

/****以下为LCA代码****/
void Dfs1(int u, int fa)
{
    dep[u] = dep[fa] + 1;
    f[u][0] = fa;
    for (int i = 1; i < 21; i++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for (auto v : G[u])
    {
        if (v == fa)
            continue;
        Dfs1(v, u);
    }
}
//倍增求树上LCA
int lca(int a, int b)
{
    if (dep[a] < dep[b])
        swap(a, b);
    for (int i = 20; i >= 0; i--)
        if (dep[f[a][i]] >= dep[b])
            a = f[a][i];
    if (a == b)
        return a;
    for (int i = 20; i >= 0; i--)
        if (f[a][i] != f[b][i])
            a = f[a][i], b = f[b][i];
    return f[a][0];
}
/****以上为LCA代码****/
void pushup(int u)
{
    int l = tr[u].l, r = tr[u].r;
    if (tr[l].sum >= tr[r].sum)
    {
        tr[u].sum = tr[l].sum;
        tr[u].col = tr[l].col;
    }
    else
    {
        tr[u].sum = tr[r].sum;
        tr[u].col = tr[r].col;
    }
}
void change(int &p, int l, int r, int typ, int k)
{
    if (!p)
        p = ++idx;
    if (l == r)
    {
        tr[p].sum += k;
        tr[p].col = typ;
        return;
    }
    int mid = l + r >> 1;
    if (typ <= mid)
        change(tr[p].l, l, mid, typ, k);
    else
        change(tr[p].r, mid + 1, r, typ, k);
    pushup(p);
}
int merge(int a, int b, int l, int r)
{
    if (!a || !b)
        return a + b;
    if (l == r)
    {
        tr[a].sum += tr[b].sum;
        return a;
    }
    int mid = l + r >> 1;
    tr[a].l = merge(tr[a].l, tr[b].l, l, mid);
    tr[a].r = merge(tr[a].r, tr[b].r, mid + 1, r);
    pushup(a);
    return a;
}
void Dfs2(int u, int fa)
{
    for (auto v : G[u])
    {
        if (v == fa)
            continue;
        Dfs2(v, u);
        root[u] = merge(root[u], root[v], 1, 100000);
    }
    ans[u] = tr[root[u]].sum == 0 ? 0 : tr[root[u]].col;
}

void solve()
{
    cin >> n >> m;
    for (int i = 1; i < n; i++)
    {
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    Dfs1(1, 0);
    while (m--)
    {
        cin >> u >> v >> w;
        change(root[u], 1, 100000, w, 1);
        change(root[v], 1, 100000, w, 1);
        int Lca = lca(u, v);
        change(root[Lca], 1, 100000, w, -1);
        change(root[f[Lca][0]], 1, 100000, w, -1);
    }
    Dfs2(1, 0);
    for (int i = 1; i <= n; i++)
    {
        cout << ans[i] << "\n";
    }
}

int main()
{
    for (int i = 2; i <= N; i++)
        Log[i] = Log[i / 2] + 1;//初始化Log2数组
    IOS int T = 1;
    // cin>>T;
    while (T--)
        solve();
    return 0;
}

/*
oxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxox
x                                                                                      o
o       _/_/_/_/                                                              _/       x
x      _/                                                                              o
o     _/_/_/_/ _/  _/_/   _/_/   _/_/_/ _/_/   _/_/_/     _/_/    _/_/_/    _/ _/   _/ x
x    _/       _/_/     _/    _/ _/   _/   _/  _/    _/ _/    _/  _/    _/  _/   _/ _/  o
o   _/       _/       _/    _/ _/   _/   _/  _/    _/ _/    _/  _/    _/  _/    _/_/   x
x  _/       _/         _/_/   _/   _/   _/  _/_/_/     _/_/ _/ _/    _/  _/      _/    o
o                                          _/                           _/      _/     x
x                                         _/                        _/_/       _/      o
o                                                                                      x
xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo
*/
相关推荐
孙小二写代码17 分钟前
[leetcode刷题]面试经典150题之1合并两个有序数组(简单)
算法·leetcode·面试
QXH20000017 分钟前
数据结构—单链表
c语言·开发语言·数据结构
imaima66619 分钟前
数据结构----栈和队列
开发语言·数据结构
little redcap22 分钟前
第十九次CCF计算机软件能力认证-1246(过64%的代码-个人题解)
算法
David猪大卫39 分钟前
数据结构修炼——顺序表和链表的区别与联系
c语言·数据结构·学习·算法·leetcode·链表·蓝桥杯
Iceberg_wWzZ40 分钟前
数据结构(Day14)
linux·c语言·数据结构·算法
夏天天天天天天天#1 小时前
求Huffman树及其matlab程序详解
算法·matlab·图论
Infedium1 小时前
优数:助力更高效的边缘计算
算法·业界资讯
student.J1 小时前
傅里叶变换
python·算法·傅里叶
五味香1 小时前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法