树上启发式合并
树上启发式合并
1、给一棵根为 \(1\) 的树,每次询问子树颜色种类数。
2、模版:洛谷U41492。
3、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(n+m)\)。
4、启发式合并主要依靠的是灵活的树的 \(dfs\) 遍历,是利用重儿子的子树大小 \(\ge \frac{n}{2}\),使得总共遍历次数不超过 \(nlogn\) 次,具体遍历操作为:
- 先遍历 \(u\) 的轻(非重)儿子,并计算答案,但不保留遍历后它对 \(cnt\) 数组的影响;
- 遍历它的重儿子,保留它对 \(cnt\) 数组的影响;
- 再次遍历 \(u\) 的轻儿子的子树结点,加入这些结点的贡献,以得到 \(u\) 的答案。
template<typename T>
struct tree_inspired{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
int n, m, k, root, cnt_edg, tot_dfn, tot_col;// dfn序列编号,当前颜色种类数
vector<node> edg;
vector<int> head;
vector<T> siz, son, fa, col;
vector<T> L, R, id, cnt, ans;
// L、R记录每个节点dfn序列的进入编号和出去编号、主要是用来记录每个节点的子树的范围,id,cnt记录每种颜色此时的数目,ans记录答案
tree_inspired(int n_, int m_, int k_, int root_ = 1)
: edg(m_ << 1 | 1), siz(n_ + 1), son(n_ + 1),
fa(n_ + 1), col(n_ + 1), L(n_ + 1), R(n_ + 1),
id(n_ + 1), cnt(k_ + 1), ans(n_ + 1), head(n_ + 1) {
n = n_;
m = m_;
k = k_;
root = root_;
cnt_edg = tot_dfn = tot_col = 0;
}
inline void add(int u, int v, const T &w) {
++cnt_edg;
edg[cnt_edg].to = v;
edg[cnt_edg].nxt = head[u];
head[u] = cnt_edg;
}
inline void add(int x) {
x = col[x];
cnt[x]++;
if (cnt[x] == 1) {
++tot_col;
}
}
inline void del(int x) {
x = col[x];
cnt[x]--;
if (!cnt[x]) {
--tot_col;
}
}
inline void dfs1(int u, int pa) {
fa[u] = pa;
siz[u] = 1;
L[u] = ++tot_dfn;// 节点的起点
id[tot_dfn] = u;
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa) {
continue;
}
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) {
son[u] = v;
}
}
R[u] = tot_dfn;// 节点的终点
}
inline void dfs2(int u, int pa, bool ok) {
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa || v == son[u]) {
continue;
}
dfs2(v, u, false);
}
if (son[u]) {// 重儿子最后遍历,使得时间复杂度成log
dfs2(son[u], u, true);
}
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa || v == son[u]) {
continue;
}
for (int j = L[v]; j <= R[v]; j++) {
add(id[j]);
}
}
add(u);
ans[u] = tot_col;
if (!ok) {// 重儿子这条链上的都不需要删除掉、不需要重置掉
for (int i = L[u]; i <= R[u]; i++) {
del(id[i]);
}
}
}
inline void init(int n_, int m_, int k_, int root_ = 1) {
n = n_;
m = m_;
k = k_;
root = root_;
cnt_edg = tot_dfn = tot_col = 0;
edg.assign(m_ << 1 | 1, node());
siz.assign(n_ + 1, 0);
son.assign(n_ + 1, 0);
fa.assign(n_ + 1, 0);
col.assign(n_ + 1, 0);
L.assign(n_ + 1, 0);
R.assign(n_ + 1, 0);
id.assign(n_ + 1, 0);
cnt.assign(k_ + 1, 0);
ans.assign(n_ + 1, 0);
head.assign(n_ + 1, 0);
}
inline void deal() {
dfs1(root, 0);
dfs2(root, 0, false);
}
};
void solve() {
int n;
std::cin >> n;
tree_inspired<int> tr(n, n - 1, n);
for (int i = 1, u, v, w; i < n; i++) {
std::cin >> u >> v;
tr.add(u, v, w);
tr.add(v, u, w);
}
for (int i = 1; i <= n; i++) {
std::cin >> tr.col[i];
}
tr.deal();
int m;
std::cin >> m;
for (int i = 1; i <= m; i++) {
int x;
std::cin >> x;
std::cout << tr.ans[x] << '\n';
}
}
5、
提升题 \(1\):洛谷CF600E。
提升题 \(2\):洛谷CF1709E。
拓展
1、树上启发式合并之求子树内数目大于等于 \(k\) 的颜色数目。
2、时间复杂度:\(O(nlog^{2}n)\),空间复杂度:\(nlogn\)。
3、模版:洛谷CF375D。
4、其实就是原始的版子上多加个树状数组维护一下每个不同种类数目的次数即可。
template<typename T>
struct Fenwick{
int n;
vector<T> a;
Fenwick() {}
Fenwick(int n_) : a(n_ + 1) {
n = n_;
}
inline int low_bit(int x) {
return x & -x;
}
inline void add(int x, const T &v) {
for (int i = x; i <= n; i += low_bit(i)) {
a[i] += v;
}
}
inline T query(int x) {
T sum{};
for (int i = x; i >= 1; i -= low_bit(i)) {
sum += a[i];
}
return sum;
}
inline T rangeQuery(int L, int R) {
return query(R) - query(L - 1);
}
inline int select(const T &k) {
int x = 0;
T cnt{};
for (int i = 1 << __lg(n); i; i >>= 1) {
if (x + i <= n && cnt + a[i + x] <= k) {
x += i;
cnt += a[x];
}
}
return x;
}
};
template<typename T>
struct tree_inspired{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
int n, m, k, q, root, cnt_edg, tot_dfn;
vector<node> edg;
vector<int> head;
vector<T> siz, son, fa, col;
vector<T> L, R, id, cnt, ans;
vector<vector<PI>> g;
Fenwick<T> tr;
tree_inspired(int n_, int m_, int k_, int q_, const int root_ = 1)
: edg(m_ << 1 | 1), siz(n_ + 1), son(n_ + 1),
fa(n_ + 1), col(n_ + 1), L(n_ + 1), R(n_ + 1),
id(n_ + 1), cnt(k_ + 1), ans(q_ + 1), head(n_ + 1),
g(n_ + 1) {
n = n_;
m = m_;
k = k_;
q = q_;
root = root_;
cnt_edg = tot_dfn = 0;
tr = Fenwick<int>(k_);
}
inline void add(int u, int v, const T &w) {
++cnt_edg;
edg[cnt_edg].to = v;
edg[cnt_edg].nxt = head[u];
head[u] = cnt_edg;
}
inline void add(int x) {
x = col[x];
if (cnt[x]) tr.add(cnt[x], -1);
cnt[x]++;
tr.add(cnt[x], 1);
}
inline void del(int x) {
x = col[x];
tr.add(cnt[x], -1);
cnt[x]--;
if (cnt[x]) tr.add(cnt[x], 1);
}
inline void dfs1(int u, int pa) {
fa[u] = pa;
siz[u] = 1;
L[u] = ++tot_dfn;
id[tot_dfn] = u;
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa) {
continue;
}
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) {
son[u] = v;
}
}
R[u] = tot_dfn;
}
inline void dfs2(int u, int pa, bool ok) {
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa || v == son[u]) {
continue;
}
dfs2(v, u, false);
}
if (son[u]) {
dfs2(son[u], u, true);
}
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa || v == son[u]) {
continue;
}
for (int j = L[v]; j <= R[v]; j++) {
add(id[j]);
}
}
add(u);
for (auto [x, y] : g[u]) {
ans[y] = tr.rangeQuery(x, n);
}
if (!ok) {
for (int i = L[u]; i <= R[u]; i++) {
del(id[i]);
}
}
}
inline void init(int n_, int m_, int k_, int q_, const int root_ = 1) {
n = n_;
m = m_;
k = k_;
q = q_;
root = root_;
cnt_edg = tot_dfn = 0;
tr = Fenwick<int>(k_);
edg.assign(m_ << 1 | 1, node());
siz.assign(n_ + 1, 0);
son.assign(n_ + 1, 0);
fa.assign(n_ + 1, 0);
col.assign(n_ + 1, 0);
L.assign(n_ + 1, 0);
R.assign(n_ + 1, 0);
id.assign(n_ + 1, 0);
cnt.assign(k_ + 1, 0);
ans.assign(q_ + 1, 0);
head.assign(n_ + 1, 0);
g.assign(n_ + 1, vector<PI>());
}
inline void deal() {
dfs1(root, 0);
dfs2(root, 0, false);
}
};
void solve() {
int n, q;
std::cin >> n >> q;
tree_inspired<int> tr(n, n - 1, 100000, q);
for (int i = 1; i <= n; i++) {
std::cin >> tr.col[i];
}
for (int i = 1, u, v; i < n; i++) {
std::cin >> u >> v;
tr.add(u, v, 1);
tr.add(v, u, 1);
}
for (int i = 1; i <= q; i++) {
int u, k;
std::cin >> u >> k;
tr.g[u].push_back({k, i});
}
tr.deal();
for (int i = 1; i <= q; i++) {
cout << tr.ans[i] << '\n';
}
}

浙公网安备 33010602011771号