洛谷P3605 線段樹合併

題目鏈接:https://www.luogu.com.cn/problem/P3605
題目大意:給出一顆樹,每個點都有一個權值,最後對於每個點,輸出在它的子樹中,有多少個點的權值比它大。
在這裏插入圖片描述
把權值取負,就是p[j]<p[i]的節點個數。直接線段樹合併。

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5+5;

int w[N], p[N], lc[N*20], rc[N*20], rt[N*20], tot=0, n;
vector<int> v[N];
int tre[N*20], ans[N];//節點信息

void Insert(int &i, int l, int r, int x){//建樹
    if(r<l) return;
    i=++tot;//動態開點
    if(l==r){
        tre[i]++; return ;
    }
    int mid=(l+r)>>1;
    if(x<=mid) Insert(lc[i], l, mid, x);
    if(x>mid) Insert(rc[i], mid+1, r, x);
    tre[i]=tre[lc[i]]+tre[rc[i]];//權值在區間[l, r]的元素個數
}

int Merge(int x, int y){//合併
    if(!x) return y;
    if(!y) return x;
    lc[x]=Merge(lc[x], lc[y]);
    rc[x]=Merge(rc[x], rc[y]);
    tre[x]=tre[lc[x]]+tre[rc[x]];//節點信息合併

    return x;
}

int query(int root, int l, int r, int x){//查詢子樹中權值<=x的節點個數
    if(!root) return 0;
    if(!x) return 0;
    if(x==r) return tre[root];

    int mid=(l+r)>>1;
    if(x>mid) return tre[lc[root]]+query(rc[root], mid+1, r, x);
    else  return query(lc[root], l, mid, x);
}

void dfs(int u, int fa){
    for(int i=0; i<v[u].size(); i++){
        int to=v[u][i];
        if(to!=fa){
            dfs(to, u);
            rt[u]=Merge(rt[u], rt[to]);//換根合併
        }
    }
    ans[u]=query(rt[u], 1, n, w[u]-1);//查詢
}

int main(){

    scanf("%d", &n);
    for(int i=1; i<=n; i++){
        scanf("%d", &w[i]);
        w[i]=-w[i], p[i]=w[i];
    }
    sort(p+1, p+n+1);
    int cut=unique(p+1, p+n+1)-p-1;
    for(int i=1; i<=n; i++) w[i]=lower_bound(p+1, p+cut+1, w[i])-p;
    for(int i=2; i<=n; i++){
        int x; scanf("%d", &x);
        v[i].push_back(x); v[x].push_back(i);
    }
    for(int i=1; i<=n; i++){
        Insert(rt[i], 1, n, w[i]);//建樹
    }
    dfs(1, -1);
    for(int i=1; i<=n; i++){
        printf("%d\n", ans[i]);
    }

    return 0;
}
/*
5
804289384
846930887
681692778
714636916
957747794
1
1
2
3

2
0
1
0
0

*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章