题意
题解
求树上最大节点子集,若子集中 j j j 为 i i i 的子孙节点,则有 w j ≥ w i w_j\geq w_i wj≥wi。
参考 L I S LIS LIS 问题的求解思路,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 为以 i i i 为根的子树上规模为 j j j 的满足条件节点子集中最小点权的最大值,此时 d p [ i ] dp[i] dp[i] 的规模,即 j j j 的最大值为答案。
考虑如何合并子树信息,以 k k k 为根的子树上的 d p [ k ] [ j ] dp[k][j] dp[k][j] 显然是随 j j j 增加递减的,两颗子树 k , w k,w k,w 的节点间没有限制,可以任意合并,那么对 d p [ k ] , d p [ w ] dp[k],dp[w] dp[k],dp[w] 归并排序即可。最后考虑如何处理 i i i 的信息,设子树合并的信息为 d p [ i ′ ] dp[i'] dp[i′], w i w_i wi 可以以满足 d p [ i ′ ] [ j ] , d p [ i ′ ] [ j ] ≥ w i dp[i'][j],dp[i'][j]\geq w_i dp[i′][j],dp[i′][j]≥wi 对应的节点为子孙节点,那么将 w i w_i wi 替换 d p [ i ′ ] dp[i'] dp[i′] 中满足 d p [ i ′ ] [ j ] < w i dp[i'][j]<w_i dp[i′][j]<wi 的最大值,此时得到 d p [ i ] dp[i] dp[i]。
对每个节点 i i i 建立一颗动态开点的权值线段树代替 d p [ i ] dp[i] dp[i],支持单点修改,区间查询;同时可以二分查找每次子树根节点需要替换的节点,设 [ 0 , w i ) [0,w_i) [0,wi) 的点数和为 s s s,满足条件的节点为恰好满足 [ 0 , j ] [0,j] [0,j] 的点数和等于 s s s 的节点 j j j。对领导树进行 D F S DFS DFS,采用线段树合并维护 d p [ i ] dp[i] dp[i] 的归并过程。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005, maxt = 19 * maxn;
struct node
{
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define sum(x) tree[x].sum
int ls, rs, sum;
} tree[maxt];
int N, W[maxn], nw, sw[maxn];
int num, rt[maxn];
int tot, head[maxn], to[maxn], nxt[maxn];
void insert(int x, int d, int &p, int l, int r)
{
if (!p)
p = ++num;
if (r - l == 1)
{
sum(p) += d;
return;
}
int m = (l + r) >> 1;
x < m ? insert(x, d, ls(p), l, m) : insert(x, d, rs(p), m, r);
sum(p) = sum(ls(p)) + sum(rs(p));
}
int merge(int p, int q, int l, int r)
{
if (!p || !q)
return p + q;
if (r - l == 1)
{
sum(p) += sum(q);
return p;
}
int m = (l + r) >> 1;
ls(p) = merge(ls(p), ls(q), l, m), rs(p) = merge(rs(p), rs(q), m, r);
sum(p) = sum(ls(p)) + sum(rs(p));
return p;
}
void change(int x, int d, int p, int l, int r)
{
if (r - l == 1)
{
sum(p) += d;
return;
}
int m = (l + r) >> 1;
x < m ? change(x, d, ls(p), l, m) : change(x, d, rs(p), m, r);
sum(p) = sum(ls(p)) + sum(rs(p));
}
int ask(int a, int b, int p, int l, int r)
{
if (r <= a || b <= l)
return 0;
if (a <= l && r <= b)
return sum(p);
int m = (l + r) >> 1;
return ask(a, b, ls(p), l, m) + ask(a, b, rs(p), m, r);
}
int find(int x, int p, int l, int r)
{
if (r - l == 1)
return l;
int m = (l + r) >> 1;
return sum(ls(p)) >= x ? find(x, ls(p), l, m) : find(x - sum(ls(p)), rs(p), m, r);
}
void dfs(int x)
{
for (int i = head[x], y; i; i = nxt[i])
y = to[i], dfs(y), rt[x] = merge(rt[x], rt[y], 0, nw);
int t = ask(0, W[x], rt[x], 0, nw), z;
if (t)
z = find(t, rt[x], 0, nw), change(z, -1, rt[x], 0, nw);
}
inline void add(int x, int y) { to[++tot] = y, nxt[tot] = head[x], head[x] = tot; }
int main()
{
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d", W + i), sw[i] = W[i];
sort(sw + 1, sw + N + 1);
nw = unique(sw + 1, sw + N + 1) - (sw + 1);
for (int i = 1; i <= N; ++i)
W[i] = lower_bound(sw + 1, sw + nw + 1, W[i]) - (sw + 1);
for (int i = 1; i <= N; ++i)
insert(W[i], 1, rt[i], 0, nw);
for (int i = 1, v; i < N; ++i)
scanf("%d", &v), add(v, i + 1);
dfs(1), printf("%d\n", sum(rt[1]));
return 0;
}

博客详细介绍了如何解决FJOI2018竞赛中的领导集团问题,即寻找树上满足特定条件的最大节点子集。该问题采用了类似于LIS(最长递增子序列)的思路,利用动态开点的权值线段树来维护以每个节点为根的子树中的信息。通过DFS遍历树并进行线段树的合并操作,最终找到满足条件的最大子集。文章内容深入浅出,适合对算法和数据结构感兴趣的读者。

1057

被折叠的 条评论
为什么被折叠?



