ACM 数据结构与算法思想记录
老年 ACMer 尝试对抗阿尔茨海默病(
图论
DFS序 \(O(n \log n)\) - \(O(1)\) Lca
考虑点\(u\),\(v\) 及其 \(Lca\) 点\(l\),不妨设 $dfn_u \lt dfn_v $,那么有 \(dfs\) 序从 \(l\) 到 \(u\) 递增,此后回到 \(l\) 后再向着 \(v\)递增
所以,按\(dfs\)序构成的序列中,\(dfn_u\)到\(dfn_v\)这一段中深度最小的点一定是\(l\)在\(v\)方向直接连接的节点
所以可以直接使用\(ST\)表维护父节点\(dfs\)序的最小值即可
注意可能有\(u\)为\(v\)的祖先的情况,因此\(ST\)表询问区间该为\([dfn_u+1,dfn_v]\)进行规避
点击查看代码
int mindfn(int x, int y) {
if (dfn[x] < dfn[y]) return x;
return y;
}
struct ST {
int st[MAXN][25];
void init(int n) {
int t = std::__lg(n) + 1;
for (int i = 1; i <= n; i++) st[dfn[i]][0] = fa[i];
for (int j = 1; j <= t; j++) {
for (int i = 1; i + (1 << (j - 1)) <= n; i++) {
st[i][j] = mindfn(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r) {
int t = std::__lg(r - l + 1);
return mindfn(st[l][t], st[r - (1 << t) + 1][t]);
}
}st;
auto Lca = [&](int x, int y) {
if (dfn[x] > dfn[y]) std::swap(x, y);
return st.query(dfn[x] + 1, dfn[y]);
};
Dsu on tree
当涉及子树问题时,对于父亲相同的子树,如果在计算一个的答案时保留了其他子树的信息,统计会出现混乱。但是这种保留对于父亲子树的统计有利
于是考虑对于每个子树,先计算它轻儿子子树中的答案,并且不保留。最后计算重儿子的答案并保留,将轻儿子的答案往上合并
对于每个节点,每当其到根节点有一条轻链时会被暴力合并一次,由树链剖分的性质可知最多会被合并 \(O(\log n)\)次
点击查看代码
#include <bits/stdc++.h>
#define ll long long
const int MAXN = 1e5 + 5;
int n, a[MAXN], fa[MAXN], siz[MAXN], son[MAXN];
std::vector <int> G[MAXN];
ll ans[MAXN];
int buc[MAXN], mx, S;
ll sum;
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
for (int i = 1; i <= n; i++) std::cin >> a[i];
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
auto dfs1 = [&](int u, int f, auto self) -> void {
fa[u] = f; siz[u] = 1;
for (const auto &v : G[u]) {
if (v == f) continue;
self(v, u, self);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
};
dfs1(1, 0, dfs1);
auto calc = [&](int u, int delta, auto self) -> void {
buc[a[u]] += delta;
if (buc[a[u]] > mx) {
mx = buc[a[u]];
sum = a[u];
}
else if (buc[a[u]] == mx) {
sum += a[u];
}
for (const auto &v : G[u]) {
if (v == fa[u] || v == S) continue;
self(v, delta, self);
}
};
auto dfs2 = [&](int u, bool keep, auto self) -> void {
for (const auto &v : G[u]) {
if (v == fa[u] || v == son[u]) continue;
self(v, 0, self);
}
if (son[u]) {
self(son[u], 1, self);
S = son[u];
}
calc(u, 1, calc);
ans[u] = sum;
S = 0;
if (!keep) {
calc(u, -1, calc);
mx = 0; sum = 0;
}
};
dfs2(1, 1, dfs2);
for (int i = 1; i <= n; i++) {
std::cout << ans[i] << " ";
}
std::cout << '\n';
}
点分治
对于树上的路径问题,我们考虑固定一个根的情况,答案分为两种:跨过根的路径(包括以根为端点)和根的子树内部的路径
显然后者有子问题结构,考虑分治。当每次的根都取子树重心时,有最少递归层数 \(O(\log n)\)
由于较小的递归层数,我们可以每一层都采取相对暴力的做法来统计跨过根的路径
注意每次向下分治时重新计算子树大小,不然会导致重心求解出错,时间复杂度假掉
点击查看代码
#include <bits/stdc++.h>
const int MAXN = 1e4 + 5;
std::vector <std::pair <int, int> > G[MAXN];
int qry[105];
bool ans[105];
std::bitset <10000005> exist;
int main() {
int n, q;
std::cin >> n >> q;
for (int i = 1; i < n; i++) {
int u, v, w;
std::cin >> u >> v >> w;
G[u].push_back({v, w});
G[v].push_back({u, w});
}
for (int i = 1; i <= q; i++) {
std::cin >> qry[i];
}
std::vector <bool> vis(n + 1);
std::vector <int> mx(n + 1), siz(n + 1);
int rt;
int node_cnt;
auto Find_size = [&](int u, int f, auto self) -> void {
siz[u] = 1;
for (const auto &p : G[u]) {
int v = p.first, w = p.second;
if (v == f || vis[v]) continue;
self(v, u, self);
siz[u] += siz[v];
}
};
auto Find_cent = [&](int u, int f, auto self) -> void {
mx[u] = 0;
for (const auto &p : G[u]) {
int v = p.first, w = p.second;
if (vis[v] || v == f) continue;
self(v, u, self);
mx[u] = std::max(mx[u], siz[v]);
}
mx[u] = std::max(mx[u], node_cnt - siz[u]);
if (mx[u] < mx[rt]) rt = u;
};
std::vector <int> dis;
auto Find_dis = [&](int u, int f, int d, auto self) -> void {
if (d > 10000000) return;
dis.push_back(d);
for (const auto & p : G[u]) {
int v = p.first, w = p.second;
if (v == f || vis[v]) continue;
self(v, u, d + w, self);
}
};
std::vector <int> used;
auto calc = [&](int u, int d, auto self) -> void {
Find_dis(u, u, d, Find_dis);
for (const auto &x : dis) {
for (int i = 1; i <= q; i++) {
if (qry[i] - x >= 0) ans[i] |= exist[qry[i] - x];
}
}
for (const auto &x : dis) {
exist[x] = 1;
used.push_back(x);
}
dis.clear();
};
auto solve = [&](int u, auto self) -> void {
vis[u] = 1; exist[0] = 1;
for (const auto &p : G[u]) {
int v = p.first, w = p.second;
if (vis[v]) continue;
calc(v, w, calc);
}
for (const auto &x : used) exist[x] = 0;
used.clear();
for (const auto &p : G[u]) {
int v = p.first;
if (vis[v]) continue;
mx[rt = 0] = 0x3f3f3f3f;
Find_size(v, u, Find_size);
node_cnt = siz[v];
Find_cent(v, u, Find_cent);
self(rt, self);
}
};
mx[rt = 0] = 0x3f3f3f3f;
node_cnt = n;
Find_size(1, 0, Find_size);
Find_cent(1, 0, Find_cent);
solve(rt, solve);
for (int i = 1; i <= q; i++) {
if (ans[i]) {
std::cout << "AYE" << '\n';
}
else std::cout << "NAY" << '\n';
}
}
点分树
考虑利用点分治只会递归\(\log n\)层的优秀性质,每次递归的时候,子树的重心向当前根节点连边,就形成了一颗\(\log n\)的树
这棵树满足对于\(l = Lca(u, v)\),必有 \(l\) 在原树 \(u\)到\(v\)的路径上
因此可以从在点分树上从\(u\)开始往上跳,假设当前跳到了\(l\),那么对\(l\)除去\(u\)方向的子树的所有节点,都有一条在原树中经过\(l\)到达\(u\)的路径,可以采用数据结构进行统计
树高的性质保证了最多向上跳 \(\log n\) 次,时间复杂度正确
点击查看代码
#include <bits/stdc++.h>
const int MAXN = 1e5 + 5;
int n, m, a[MAXN];
std::vector <int> G[MAXN];
struct Sgt {
struct Node {
int ls, rs, sum;
}t[MAXN * 80];
int cnt;
void update(int &p, int l, int r, int pos, int val) {
if (!p) p = ++cnt;
t[p].sum += val;
if (l == r) return;
int mid = (l + r) >> 1;
if (pos <= mid) update(t[p].ls, l, mid, pos, val);
else update(t[p].rs, mid + 1, r, pos, val);
}
int query(int p, int l, int r, int ql, int qr) {
if (!p || ql > qr) return 0;
if (ql <= l && r <= qr) return t[p].sum;
int mid = (l + r) >> 1, ret = 0;
if (ql <= mid) ret += query(t[p].ls, l, mid, ql, qr);
if (qr > mid) ret += query(t[p].rs, mid + 1, r, ql, qr);
return ret;
}
}t1, t2;
int rt1[MAXN], rt2[MAXN];
int lst;
int dfn[MAXN], fa[MAXN], dcnt, dep[MAXN];
void dfs(int u, int f) {
fa[u] = f; dfn[u] = ++dcnt; dep[u] = dep[f] + 1;
for (const auto &v : G[u]) {
if (v == f) continue;
dfs(v, u);
}
}
struct ST {
int st[MAXN][25];
int dfnmin(int x, int y) {
if (dfn[x] < dfn[y]) return x;
return y;
}
void init() {
int t = std::__lg(n) + 1;
for (int i = 1; i <= n; i++) st[dfn[i]][0] = fa[i];
for (int j = 1; j <= t; j++) {
for (int i = 1; i + (1 << (j - 1)) <= n; i++) {
st[i][j] = dfnmin(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r) {
int t = std::__lg(r - l + 1);
return dfnmin(st[l][t], st[r - (1 << t) + 1][t]);
}
}st;
int Lca(int x, int y) {
if (x == y) return x;
if (dfn[x] > dfn[y]) std::swap(x, y);
return st.query(dfn[x] + 1, dfn[y]);
}
int dis(int x, int y) {
int l = Lca(x, y);
return dep[x] + dep[y] - 2 * dep[l];
}
int fa2[MAXN], mx[MAXN], siz[MAXN];
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; i++) std::cin >> a[i];
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
st.init();
std::vector <bool> vis(n + 1);
int node_cnt, rt;
auto Find_cent = [&](int u, int f, auto self) -> void {
mx[u] = 0;
for (const auto &v : G[u]) {
if (vis[v] || v == f) continue;
self(v, u, self);
mx[u] = std::max(mx[u], siz[v]);
}
mx[u] = std::max(mx[u], node_cnt - siz[u]);
if (mx[rt] > mx[u]) rt = u;
};
auto Find_size = [&](int u, int f, auto self) -> void {
siz[u] = 1;
for (const auto &v : G[u]) {
if (vis[v] || v == f) continue;
self(v, u, self);
siz[u] += siz[v];
}
};
auto build = [&](int u, auto self) -> void {
vis[u] = 1; Find_size(u, -1, Find_size);
for (const auto &v : G[u]) {
if (vis[v]) continue;
mx[rt = 0] = 0x3f3f3f3f;
node_cnt = siz[v];
Find_cent(v, u, Find_cent);
fa2[rt] = u;
self(rt, self);
}
};
mx[rt = 0] = 0x3f3f3f3f;
node_cnt = n;
Find_cent(1, 0, Find_cent);
fa2[rt] = 0;
build(rt, build);
auto query = [&](int x, int k) {
int f = fa2[x], cur = x, ret = t1.query(rt1[x], 0, n, 0, k);
while (f) {
int d = k - dis(x, f);
ret += (t1.query(rt1[f], 0, n, 0, d) - t2.query(rt2[cur], 0, n, 0, d));
cur = fa2[cur];
f = fa2[f];
}
return ret;
};
auto update = [&](int x, int y, bool flag) {
int cur = x;
while (cur) {
if (flag) t1.update(rt1[cur], 0, n, dis(x, cur), -a[x]);
t1.update(rt1[cur], 0, n, dis(x, cur), y);
int f = fa2[cur];
if (f) {
if (flag) t2.update(rt2[cur], 0, n, dis(x, f), -a[x]);
t2.update(rt2[cur], 0, n, dis(x, f), y);
}
cur = f;
}
};
for (int i = 1; i <= n; i++) {
update(i, a[i], 0);
}
while (m--) {
int op, x, y;
std::cin >> op >> x >> y;
x ^= lst; y ^= lst;
if (op == 0) {
std::cout << (lst = query(x, y)) << '\n';
}
if (op == 1) {
update(x, y, 1);
a[x] = y;
}
}
return 0;
}

浙公网安备 33010602011771号