【洛谷】[FJOI2018]领导集团问题

楼上两篇题解写的有一点点复杂,有map还写了离散化……

差分固然是一种理解方式,但其实有一种更好的理解方法和更简洁的代码。

那么现在我就来讲一讲

题意简述

文字语言:求树上最大权值随祖孙关系不降的点集大小

数学语言:求 \(|S_{max}|\) 使得 \(\forall{i,j(ancestor\ of \ i)\in S}, w_i\leq w_j\)

为了方便描述,我们定义这种集合为“树上LIS”。

题解

考虑采用数学归纳法

对于每一个点 \(i\) 使用multiset维护一个集合 \(f_i\) 满足以下性质

  • \(f_i\) 表示在 \(i\) 的子树中 选择 \(i\) 个点组成的树上LIS中,最小级别值 \(w\) 中最大的那一个。

  • 以 \(i\) 为根节点的 \(ans_i=|f_i|\)(\(|f_i|\)表示集合 \(f_i\) 的大小)(该性质可由上述性质发现)

对于任意一个叶子节点 \(i\), \(f_i\)显然只含有 \(w_i\),满足树上LIS性质。

再考虑不是叶子节点的 \(i\)

假设点 \(i\) 的所有孩子 \(v\) 的 \(f_v\) 已经满足求出并满足上述性质,我们应该如何求出 \(i\) 的 \(f_i\) 呢?

首先,显然 \(i\) 的所有孩子不会相互影响,可以直接将孩子们的 \(f\) 集合全部启发式合并 ,记 \(S=\bigcup_{v\in i.son}f_v\)

现在我们考虑将 \(i\) 加入 \(S\) 集合并使集合满足性质

我们直接在multiset上二分出第一个满足 \(f_j\geq f_i\) 那么我们将 \(i\) 接在 \(j\) 前显然是最优方案,此时 \(f_{j-1}\) 就可以被 \(w_i\) 替换,那么现在的集合就是我们要求的 \(f_i\),并且满足树上LIS性质。

按照这样的方式在树上dfs即可求出 \(f_1\),此时答案即为 \(|f_1|\)。

复杂度证明

该算法的复杂度为 \(O(nlog^2n)\)

考虑同样采用数学归纳法

记 \(T_i\) 表示处理出 \(f_i\) 的时间复杂度,\(S_i\)表示 \(i\) 的子树大小

我们需要证明 \(T_i=S_ilog^2S_i\)

对于任意一个叶子节点 \(i\),\(S_i=1\),此时只需在multiset中插入 \(w_i\) 复杂度为 \(O(1log1)\),满足\(T_i=S_ilog^2S_i\)

再考虑不是叶子节点的 \(i\)

假设点 \(i\) 的所有孩子 \(v\) 的 \(T_v=S_vlog^2S_v\)

那么 \(T_i=\sum_{v\in i.son}T_v+T_{merge}\)

因为子孙们包含的节点个数\(\sum_{v\in i.son}+1=S_i\)

所以\(\sum_{v\in i.son}T_v\leq S_ilog^2S_i\)

启发式合并的复杂度为 \(S_ilogS_i\),使用multiset维护加一个log,\(T_{merge}=S_ilog^2S_i\)

所以\(T_i\)与 \(S_ilog^2S_i\) 同阶

证毕。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
multiset<int> f[N];
multiset<int>::iterator it;
int n, w[N], ans;
int h[N], to[N], nxt[N], t;
bool comp(int x, int y) { return w[x] < w[y]; }
void add(int u, int v) { to[++t] = v, nxt[t] = h[u], h[u] = t; }
void merge(int u, int v) {
    if(f[u].size() < f[v].size()) swap(f[u], f[v]);
    for(it = f[v].begin(); it != f[v].end(); ++it) f[u].insert(*it);
}
void dfs(int u) {
    for(int i = h[u]; i; i = nxt[i]) dfs(to[i]), merge(u, to[i]);
    f[u].insert(w[u]);
    it = f[u].lower_bound(w[u]);
    if(it != f[u].begin()) f[u].erase(--it);
}
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    for(int i = 2; i <= n; ++i) {
        int f;
        scanf("%d", &f);
        add(f, i);
    }
    dfs(1);
    printf("%d", f[1].size());
}
上一篇:【C++ STL】Set和Multiset 怎么用咧↓↓↓


下一篇:集合-新集合类型