7.17-雨天的尾巴

雨天的尾巴

题目背景

深绘里一直很讨厌雨天。

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

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

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

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

题目描述

首先村落里的一共有 \(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\) 行的整数代表 \(i\) 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。

如果某座房屋没有救济粮,则输出 \(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\%\) 的数据,保证 \(n, m \leq 100\)
  • 对于 \(50\%\) 的数据,保证 \(n, m \leq 2 \times 10^3\)
  • 对于 \(100\%\) 测试数据,保证 \(1 \leq n, m \leq 10^5\)\(1 \leq a,b,x,y \leq n\)\(1 \leq z \leq 10^5\)

思路

吧每个点都整一个线段树,然后由于是对xy的路径上加z,所以是一个知识点树上差分。再进行线段树合并即可解决。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int N = 1e5 + 10;
const int M = N * 80;

int deep[N], fa[N][20];
int ls[M], rs[M];
int sum[M], maxz[M], root[N];
int ans[N];
vector<int> ve[N];
int cnt;
int n, m;

// 初始化(ST表)
void dfs(int u, int father)
{
    fa[u][0] = father;
    deep[u] = deep[father] + 1;
    
    for(int i = 1; i < 20; i ++)
    fa[u][i] = fa[fa[u][i - 1]][i - 1]; //ST表

    for(int i  = 0; i < ve[u].size(); i ++)
    {
        int j = ve[u][i];
        if(j != father) dfs(j, u);
    }
}

// 求lca
int lca(int u, int v)
{
    if(deep[u] < deep[v]) swap(u, v);

    for(int i = 19; i >= 0; i --)
        if(deep[fa[u][i]] >= deep[v]) u = fa[u][i];

    if(v == u) return u;

    for(int i = 19; i >= 0; i --)
    {
        if(fa[u][i] != fa[v][i])
        u = fa[u][i], v = fa[v][i];
    }

    return fa[u][0];
}

//更新数据
/*void pushup(int rt)
{
    if(sum[ls[rt]] > sum[rs[rt]])
        sum[rt] = sum[ls[rt]], maxz[rt] = maxz[ls[rt]];
    else if(sum[ls[rt]] < sum[rs[rt]])
        sum[rt] = sum[rs[rt]], maxz[rt] = maxz[rs[rt]];
    else sum[rt] = sum[rs[rt]], maxz[rt] = min(maxz[ls[rt]], maxz[rs[rt]]);
}*/

void pushup(int rt){
    if(sum[ls[rt]] >= sum[rs[rt]])
        sum[rt] = sum[ls[rt]], maxz[rt] = maxz[ls[rt]];
    else sum[rt] = sum[rs[rt]], maxz[rt] = maxz[rs[rt]];
}

//线段树动态开点
void Update(int &rt, int l, int r, int z, int val)
{
    if(!rt) rt = ++ cnt;
    if(l == r) // 找到叶节点
    {
        sum[rt] += val; //该颜色 + 1;
        maxz[rt] = z; //涂颜色;
        return;
    }

    int mid = l + r >> 1;
    if(z > mid) Update(rs[rt], mid + 1, r, z, val);
    else Update(ls[rt], l, mid, z, val);
    pushup(rt);
}

//线段树合并
int merge(int u, int v, int l, int r)
{
    if(!u || !v) return u + v;
    if(l == r)
    {
        sum[u] += sum[v];
        return u;
    }

    int mid = l + r >> 1;

    ls[u] = merge(ls[u], ls[v], l, mid);
    rs[u] = merge(rs[u],rs[v], mid + 1, r);
    pushup(u);
    return u;
}

//计算答案(递推)
void cale(int u, int father)
{
    for(int i = 0 ; i < ve[u].size(); i ++) //计算合并后的树
    {
        int j = ve[u][i];
        if(j != father)
        {
            cale(j, u);
            root[u] = merge(root[u], root[j], 1, N);
        }
    }
    ans[u] = maxz[root[u]];
	if(!sum[root[u]]) ans[u] = 0; //如果到该点的没有, 答案为0
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i < n; i ++)
    {
        int a, b;
        cin >> a >> b;
        ve[a].push_back(b);
        ve[b].push_back(a);
    }

    dfs(1, 0);

    while(m --)
    {
        int a, b, z;
        cin >> a >> b >> z;
        Update(root[a], 1, N, z, 1);
        Update(root[b], 1, N, z, 1);
        Update(root[lca(a, b)], 1, N, z, -1);
        Update(root[fa[lca(a, b)][0]], 1, N, z, -1); //树上差分
    }

    cale(1, 0);

    for(int i = 1; i <= n; i ++) cout << ans[i] << endl;

}
posted @ 2023-07-17 15:12  苏安然丫  阅读(24)  评论(0)    收藏  举报