NC22494 选点

题目链接

题目

题目描述

有一棵n个节点的二叉树,1为根节点,每个节点有一个值wi。现在要选出尽量多的点。

对于任意一棵子树,都要满足:

如果选了根节点的话,在这棵子树内选的其他的点都要比根节点的值

如果在左子树选了一个点,在右子树中选的其他点要比它

输入描述

第一行一个整数n。第二行n个整数wi,表示每个点的权值。
接下来n行,每行两个整数a,b。第i+2行表示第i个节点的左右儿子节点。没有为0。
\(n,a,b\leq10^5, -2\times10^9\leq w_i \leq 2\times 10^9\)

输出描述

一行一个整数表示答案。

示例1

输入

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

输出

3

题解

知识点:DFS序,线性dp,二分。

注意到,要求选点的大小是 \(root < right < left\) ,因此我们可以按照根右左的顺序处理出dfn序。这个序列将选点规则转化为,选取一个严格递增的一个子序列,问题就变为最长上升子序列。

我们还需要对最长上升子序列做一个优化,通常可以用权值树状数组或者在长度为下标的数组上二分,可以优化为线性对数复杂度,这里用的是后者,比较方便。

时间复杂度 \(O(n \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Graph {
    struct edge {
        int v, nxt;
    };
    int idx;
    vector<int> h;
    vector<edge> e;

    Graph(int n = 0, int m = 0) { init(n, m); }

    void init(int n, int m) {
        idx = 0;
        h.assign(n + 1, 0);
        e.assign(m + 1, {});
    }

    void add(int u, int v) {
        e[++idx] = { v,h[u] };
        h[u] = idx;
    }
};

const int N = 100007;
Graph g;
int a[N];

int dfncnt;
int dfn[N];
void dfs(int u) {
    dfn[++dfncnt] = u;
    for (int i = g.h[u];i;i = g.e[i].nxt) {
        int v = g.e[i].v;
        dfs(v);
    }
}

int lst[N];//! 长度为i的上升子序列的最小结尾

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    g.init(n, n << 1);
    for (int i = 1;i <= n;i++) cin >> a[i];
    for (int i = 1;i <= n;i++) {
        int l, r;
        cin >> l >> r;
        if (l) g.add(i, l);
        if (r) g.add(i, r);
    }
    dfs(1);
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        int pos = upper_bound(lst + 1, lst + ans + 1, a[dfn[i]]) - lst;//! 注意有效长度是ans,多了会错乱
        lst[pos] = a[dfn[i]];
        ans = max(ans, pos);
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2023-06-22 21:41  空白菌  阅读(8)  评论(0编辑  收藏  举报