Educational Codeforces Round 2 | E. Lomsat gelral
E. Lomsat gelral
题意
- 有一棵 \(n\) 个结点的以 \(1\) 号结点为根的有根树。
- 每个结点都有一个颜色,颜色是以编号表示的, \(i\) 号结点的颜色编号为 \(c_i\)。
- 如果一种颜色在以 \(x\) 为根的子树内出现次数最多,称其在以 \(x\) 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
- 你的任务是对于每一个 \(i\in[1,n]\),求出以 \(i\) 为根的子树中,占主导地位的颜色的编号和。
- \(n\le 10^5,c_i\le n\)
分析
如果只需要求某个节点的子树中占主导地位的颜色的编号和,可以直接 \(O(n)\) 遍历整个子树求得答案,但是要求 \(n\) 个节点的子树中占主导地位的颜色的编号和,暴力求每个结点的答案是不可行的。
考虑 树上启发式合并(dsu on tree),可以 \(O(n \log n)\) 的复杂度求解本题。
做法1 dsu on tree
首先 \(O(n)\) 遍历整棵树求得每个点的重儿子,然后 \(DFS\) 处理每个点的答案,对于某个点非重儿子 \(DFS\) 求解并每次清空,然后再进行重儿子的求解,最后暴力求解除了重儿子之外其他的节点。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for (int i(a); i <= b; ++ i )
#define dec(i, a, b) for (int i(b); i >= a; -- i )
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
constexpr int N = 1E5 + 10;
int n, mx, a[N], sz[N], cnt[N];
vector<int> son[N];
ll ans[N], sum;
bool is[N];
void dfs1(int u, int fa) {
sz[u] = 1; int id = 0;
for (int &v: son[u]) if (v != fa) {
dfs1(v, u); sz[u] += sz[v];
id = sz[v] > sz[id] ? v : id;
}
if (id) is[id] = true;
}
void calc(int u, int fa, int id) { //暴力求
cnt[a[u]] ++;
if (cnt[a[u]] > mx) {
sum = a[u];
mx = cnt[a[u]];
} else if (cnt[a[u]] == mx) {
sum += a[u];
}
for (int &v: son[u]) if (v != fa && v != id) {
calc(v, u, id);
}
}
void init(int u, int fa) {
cnt[a[u]] = 0;
for (int &v: son[u]) if (v != fa) {
init(v, u);
}
}
void dfs(int u, int fa) {
int id = 0;
for (int &v: son[u]) if (v != fa) {
if (!is[v]) {
dfs(v, u);
init(v, u);
sum = 0, mx = 0;
} else {
id = v;
}
}
if (id) dfs(id, u);
calc(u, fa, id);
ans[u] = sum;
}
void solve() {
cin >> n;
rep (i, 1, n) cin >> a[i];
for (int i = 1; i < n; i ++ ) {
int u, v; cin >> u >> v;
son[u].emplace_back(v);
son[v].emplace_back(u);
}
dfs1(1, 0);
dfs(1, 0);
for (int i = 1; i <= n; i ++ ) cout << ans[i] << " \n"[i == n];
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
solve();
return 0;
}
做法2 dfs序 + 分治
考虑 dfs 序上分治求解,求解 \([l, r]\) 区间内的解,令 \(mid = (l + r) / 2\)可以把所有的点分为三种
- 整颗子树都在 \([l,mid]\)的节点,通过递归求解
- 整颗子树都在 \([mid+1,r]\)的节点,通过递归求解
- 子树中的节点在两边都存在,可以通过循环求解
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for (int i(a); i <= b; ++ i )
#define dec(i, a, b) for (int i(b); i >= a; -- i )
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
constexpr int N = 1E5 + 10;
int n, cur, t, mx, q[N], a[N], sz[N], idx[N], dfn[N], cnt[N];
vector<int> son[N];
ll ans[N], sum;
void dfs1(int u, int fa) {
dfn[u] = ++ cur; idx[cur] = u; sz[u] = 1;
for (int &v: son[u]) if (v != fa) {
dfs1(v, u); sz[u] += sz[v];
}
}
void del() {
while (t) cnt[q[t -- ]] = 0;
sum = mx = 0;
}
void add(int x) {
cnt[a[x]] ++;
if (cnt[a[x]] > mx) {
sum = a[x];
mx = cnt[a[x]];
} else if (cnt[a[x]] == mx) {
sum += a[x];
}
q[++ t] = a[x];
}
void dfs2(int l, int r) {
if (l == r) {
if (sz[idx[l]] == 1) ans[idx[l]] = a[idx[l]];
return ;
}
int mid = (l + r) / 2;
dfs2(l, mid); dfs2(mid + 1, r);
int p = mid;
del(); // 注意清空
for (int i = mid; i >= l; i -- ) {
int j = i + sz[idx[i]] - 1;
if (j > r) break;
add(idx[i]);
if (j <= mid) continue;
while (p < j) add(idx[++ p]);
ans[idx[i]] = sum;
}
}
void solve() {
cin >> n;
rep (i, 1, n) cin >> a[i];
for (int i = 1; i < n; i ++ ) {
int u, v; cin >> u >> v;
son[u].emplace_back(v);
son[v].emplace_back(u);
}
dfs1(1, 0);
dfs2(1, n);
for (int i = 1; i <= n; i ++ ) cout << ans[i] << " \n"[i == n];
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
solve();
return 0;
}