线段树合并:CF600E

该算法的全称叫做:动态开点权值线段树合并.一般用来解决不好维护的子树信息.

一般来讲,线段树合并都是指的权值线段树合并.目前还没见到过非权值线段树的合并.

题目大意:

给你一颗树,每个点上有一个权值 v a l i val_i vali.现在问你,对于每一颗子树,它的出现次数最多的权值的和为多少(有多个出现次数最多的,都应算上). n ≤ 1 e 5 n \leq 1e5 n1e5

题目思路:

两种解法吧,1.dsu on tree. 2.线段树合并.基本都能当板子题使用了.

学过主席树会觉得这个东西没有违和感. 动态开n颗线段树,dfs的时候合并子树的线段树.然后线段树中查询答案就好了.

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

关键代码:如何合并线段树?

// 将以b为根的子线段树合并到a上.
int mer (int u , int v , int l , int r)
{
    if (!u) return v;
    if (!v) return u;
    if (l == r){
        mx[u] += mx[v];
        res[u] = l;
        return u;
    }
    ls[u] = mer (ls[u] , ls[v] , l , mid);
    rs[u] = mer (rs[u] , rs[v] , mid + 1 , r);
    pushup(u);
    return u;
}

注:
若u,v其中一个是空节点,那么直接将非空的节点返回给父节点连上.
若到了叶子节点,将v的信息合并到u上.
否则就递归的合并左右子树,将返回回来的节点连在u的左右子树上.

AC代码:

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define mid ((l + r) >> 1)
using namespace std;
const int maxn = 1e5 + 4;
int mx[maxn << 5] ,  ls[maxn << 5] , rs[maxn << 5] , rt[maxn] , tot;
ll res[maxn << 5];
void pushup (int t)
{
    int tl = ls[t] , tr = rs[t];
    mx[t] = max (mx[tl] , mx[tr]);
    if (mx[tl] == mx[tr]) res[t] = res[tl] + res[tr];
    else res[t] = (mx[tl] > mx[tr] ? res[tl] : res[tr]);
}
int add (int l , int r , int t , int p)
{
    int now = ++tot;
    ls[now] = ls[t];
    rs[now] = rs[t];
    if (l == r) {
        mx[now]++;
        res[now] = p;
        return now;
    }
    if (p <= mid) ls[now] = add(l , mid , ls[now] , p);
    else rs[now] = add (mid + 1 , r , rs[now] , p);
    pushup(now);
    return now;
}
int mer (int u , int v , int l , int r)
{
    if (!u) return v;
    if (!v) return u;
    if (l == r){
        mx[u] += mx[v];
        res[u] = l;
        return u;
    }
    ls[u] = mer (ls[u] , ls[v] , l , mid);
    rs[u] = mer (rs[u] , rs[v] , mid + 1 , r);
    pushup(u);
    return u;
}
vector<int> E[maxn];
ll ans[maxn];
int a[maxn] , n;
void dfs (int u , int fa)
{
    rt[u] = add(1 , n , rt[0] , a[u]);
    for (auto v : E[u]){
        if (v == fa) continue;
        dfs (v , u);
        mer(rt[u] , rt[v] , 1 , n);
    }
    ans[u] = res[rt[u]];
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n ; i++){
        cin >> a[i];
    }
    for (int i = 1; i < n ; i++){
        int x , y; cin >> x >> y;
        E[x].pb (y);
        E[y].pb (x);
    }
    dfs (1 , 0);
    for (int i = 1; i <= n ; i++)
        cout << ans[i] << " ";
    cout << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值