倍增,DFS序,欧拉序和树的一些知识
2022/8/11 6:26:54
本文主要是介绍倍增,DFS序,欧拉序和树的一些知识,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
倍增
定义
倍增法,顾名思义就是翻倍.
它能够使线性的处理转化为对数级的处理,大大地优化时间复杂度
这个方法在很多算法中均有应用,其中最常用的是 RMQ 问题和求LCA,无修改的路径信息。
路径最小值
注意:路径上的信息需要可以合并,例如求最值
const int N = 201000; const int LOGN = 18; int n, q; int dep[N], par[N][LOGN + 1], val[N][LOGN + 1]; vector< pair<int,int> > e[N]; il void dfs(int u, int f) { dep[u] = dep[f] + 1; for (auto p : e[u]) { int v = p.fi; if (v == f) continue; par[v][0] = u; val[v][0] = p.second; dfs(v, u); } } int query(int u, int v) { int ans = 1 << 30; if (dep[u] > dep[v]) swap(u, v); int d = dep[v] - dep[u]; per(j,LOGN,0) if (d & (1 << j)) { ans = min(ans, val[v][j]); v = par[v][j]; } if (u == v) return ans; per(j,LOGN,0) if (par[u][j] != par[v][j]) { ans = min(ans, min(val[u][j], val[v][j])); u = par[u][j]; v = par[v][j]; } ans = min({ans, val[u][0], val[v][0]}); return ans; } int main(void) { clock_t c1 = clock(); FastIO(); cin >> n >> q; rep(i,1,n-1) { int u, v, w; cin >> u >> v >> w; e[u].pb(make_pair(v,w)); e[v].pb(make_pair(u,w)); } dfs(1,0); //预处理掉这部分数据 rep(j,1,LOGN) rep(u,1,n) { par[u][j] = par[par[u][j-1]][j-1]; val[u][j] = min(val[u][j-1], val[par[u][j-1]][j-1]); } rep(i,1,q) { int u, v; cin >> u >> v; cout << query(u, v) << '\n'; } cerr << "Time Used:" << clock() - c1 << "ms" << endl; return 0; }
DFS序
定义
树的DFS序列,也就是树的深搜序,它的概念是:树的每一个节点在深度优先遍历中进出栈的时间序列。
作用
可以把子树问题转化为序列问题,非线性问题变成线性问题求解,可以用上树状数组或者线段树等数据结构进行信息维护。
可以用来做带修改的子树信息这一类题目。
例题1
题面
给一颗n个点的树,有两个操作。
- 1 x y, 把x的点权修改成y
- 2 x,询问x点的子树点权和和到根的路径的点权和(含x点)
代码实现
两个树状数组,分别维护前缀和和差分。
前缀和数组用来维护子树点权和,差分数组用来维护到根的路径点全和。
const int N = 2e5 + 10; template<class T> struct BIT { T c[N]; int size; void resize(int s) { size = s; rep(i,1,size) c[i] = 0; } void modify(int x, T d) { assert(x != 0); for (; x <= size; x += x & (-x)) c[x] += d; } T query(int x) { assert(x<=size); T s = 0; for (; x; x -= x & (-x)) s += c[x]; return s; } }; int n, q, tot; int a[N],l[N],r[N]; vector<int> e[N]; BIT<ll> c1, c2; il void dfs(int u, int f) { l[u] = ++tot; for (auto v : e[u]) if (v == f) continue; else dfs(v, u); r[u] = tot; } int main(void) { // clock_t c1 = clock(); FastIO(); cin >> n >> q; rep(i,2,n) { int u, v; cin >> u >> v; e[u].pb(v), e[v].pb(u); } dfs(1, 0); c1.resize(n), c2.resize(n); rep(i,1,n) { cin >> a[i]; c1.modify(l[i], a[i]); c2.modify(l[i], a[i]); c2.modify(r[i] + 1, -a[i]); } rep(i,1,q) { int ty; cin >> ty; if (ty == 1) { int x, y; cin >> x >> y; int d = y - a[x]; a[x] = y; c1.modify(l[x], d); c2.modify(l[x], d); c2.modify(r[x] + 1, -d); } else { int x; cin >> x; cout << c1.query(r[x]) - c1.query(l[x] - 1) << " "; cout << c2.query(l[x]) << '\n'; } } // cerr << "Time Used:" << clock() - c1 << "ms" << endl; return 0; }
DFS序列2
代码实现
const int N = 2e5 + 10; template<class T> struct BIT { T c[N]; int size; void resize(int s) { size = s; rep(i,1,size) c[i] = 0; } void modify(int x, T d) { assert(x != 0); for (; x <= size; x += x & (-x)) c[x] += d; } T query(int x) { assert(x<=size); T s = 0; for (; x; x -= x & (-x)) s += c[x]; return s; } }; int n, q, tot; int a[N],l[N],r[N]; vector<int> e[N]; vector< pair<int, int> > son[N]; BIT<ll> c1; void dfs(int u, int f) { l[u] = ++tot; for (auto v : e[u]) { if (v == f) continue; else dfs(v, u); son[u].pb({l[v], r[v]}); } r[u] = tot; } int main(void) { // clock_t c1 = clock(); ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); cin >> n >> q; rep(i,2,n) { int u, v; cin >> u >> v; e[u].pb(v), e[v].pb(u); } int root = 1; dfs(1, 0); c1.resize(n); rep(i,1,n) { cin >> a[i]; c1.modify(l[i], a[i]); } rep(i,1,q) { int ty; cin >> ty; if (ty == 1) { int x, y; cin >> x >> y; int d = y - a[x]; a[x] = y; c1.modify(l[x], d); } else if (ty == 3) { cin >> root; } else { int x; cin >> x; if (x == root) cout << c1.query(n) << '\n'; else if (l[x] < l[root] && r[root] <= r[x]) { auto seg = *prev(upper_bound(all(son[x]), pair<int,int>{l[root], r[root]})); cout << c1.query(n) - (c1.query(seg.second) - c1.query(seg.first - 1)) << '\n'; } else cout << c1.query(r[x]) - c1.query(l[x] - 1) << '\n'; } } return 0; }
欧拉序
相比DFS序,多记录一层回溯的点。
const int N = 501000, LOGN = 20; int n, q, p[N], tot, dep[N]; vector<int> e[N]; PII f[LOGN + 2][N * 2]; ll ans; unsigned int A, B, C; inline unsigned int rng61() { A ^= A << 16; A ^= A >> 5; A ^= A << 1; unsigned int t = A; A = B; B = C; C ^= t ^ A; return C; } void dfs(int u, int par) { p[u] = ++tot; dep[u] = dep[par] + 1; f[0][tot] = {dep[u], u}; for (auto v : e[u]) { if (v == par) continue; dfs(v, u); f[0][++tot] = {dep[u], u}; } } int main(){ scanf("%d%d%u%u%u", &n, &q, &A, &B, &C); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d",&u,&v); e[u].pb(v); e[v].pb(u); } dfs(1, 0); for (int j = 1; j <= LOGN; j++) for (int i = 1; i + (1 << j) - 1 <= tot; i++) f[j][i] = min(f[j-1][i], f[j - 1][i + (1 << (j - 1))]); for (int i = 1; i <= q; i++) { int l = p[rng61() % n + 1], r = p[rng61() % n + 1]; if (l > r) swap(l, r); int len = 31 - __builtin_clz(r - l + 1); int d = min(f[len][l], f[len][r - (1 << len) + 1]).second; ans ^= 1ll * i * d; } printf("%lld\n",ans); return 0; }
树
树的直径
树的直径是指树上任意两个节点之间最长.
树的直径的中间节点被称为树的中心,如果直径上有偶数个点,那么中间的两个节点都可以是树的中心.
树的中心到其他点的最长路径最短.
代码实现
int n, pre[N], c[N], q[N], dist[N]; int l, front = 1, rear = 0; vector<int> e[N]; il void dfs(int x) { for (auto y : e[x]) { if (y != pre[x]) { pre[y] = x; dist[y] = dist[x] + 1; dfs(y); } } } int main(void) { FastIO(); cin >> n; rep(i,1,n-1) { int u, v; cin >> u >> v; e[u].pb(v), e[v].pb(u); } memset(dist, 0, sizeof(dist)); memset(pre, 0, sizeof(pre)); pre[1] = -1; dfs(1); int idx = 0, v = 0; rep(i,1,n) { if (dist[i] > v) v = dist[i], idx = i; } memset(dist, 0, sizeof(dist)); memset(pre, 0, sizeof(pre)); pre[idx] = -1; dfs(idx); v = 0; rep(i,1,n) v = max(v, dist[i]); cout << v << '\n'; return 0; }
树的重心
对于一颗无根树而言,当一个节点被选为根节点,它底下的每一个子节点的子树的大小最大值最小的那个点,被称为树的重心
删除重心后,树分裂成若干个子树,这若干个子树中的最大值最小.
性质
- 当重心为根节点时,它底下的每一个子树大小不大于整棵树大小的一半
- 重心到其他所有节点的距离和最小
代码实现
小数据\(O(logn)\)
const int N = 1e5 + 10; int n, pre[1001], c[N], q[N], dist[N], f[N]; int l, front = 1, rear = 0; vector<int> e[N]; il void dfs(int x) { for (auto y : e[x]) { if (y != pre[x]) { pre[y] = x; dist[y] = dist[x] + 1; dfs(y); } } } il void solve(int x) { ++cnt; for (auto y : e[x]) if (y != pre[x]) pre[y] = x, solve(y); } int main(void) { FastIO(); cin >> n; rep(i,1,n-1) { int u, v; cin >> u >> v; e[u].pb(v), e[v].pb(u); } rep(i,1,n) { f[i] = 0; memset(pre, 0, sizeof(pre)); for (auto y : e[i]) { cnt = 0; pre[y] = i; solve(y); f[i] = max(f[i], cnt); } } // idx 重心 int idx = 0, v = 1 << 30; rep(i,1,n) if (f[i] < v) v = f[i], idx = i; memset(dist, 0, sizeof(dist)); memset(pre, 0, sizeof(pre)); pre[idx] = -1; dfs(idx); int ans = 0; rep(i,1,n) ans += dist[i]; cout << ans << '\n'; return 0; }
大数据
时间复杂度\(O(logn)\)
#include <bits/stdc++.h> using namespace std; #define DABIAO freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define debug(x...) do { cout << "\033[32;1m" << #x << " --> "; rd_debug(x); } while (0) void rd_debug() {cout << "\033[39;0m" << endl; } template<class T, class... Ts> void rd_debug(const T& arg, const Ts&... args) { cout << arg << " "; rd_debug(args...); } #define FastIO() ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr) #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(x) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() #define il inline typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 0x3f3f3f3f; const ll infl = 0x3f3f3f3f3f3f3f3fll; const double eps = 1e-6; const int N = 1e5 + 10; int n, pre[1001], c[N], q[N], dist[N], f[N]; int l, front = 1, rear = 0; vector<int> e[N]; il void dfs(int x) { for (auto y : e[x]) { if (y != pre[x]) { pre[y] = x; dist[y] = dist[x] + 1; dfs(y); } } } il void solve(int x) { s[x] = 1; for (auto y : e[x]) if (y != pre[x]) pre[y] = x, solve(y), s[x] += s[y]; } int main(void) { FastIO(); cin >> n; rep(i,1,n-1) { int u, v; cin >> u >> v; e[u].pb(v), e[v].pb(u); } memset(pre, 0, sizeof(pre)); pre[1] = -1; solve(1); int idx = 0, v = 1 << 30; rep(i,1,n) { int f = 0; for (auto y : e[i]) { if (y != pre[i]) f = max(f, s[y]); else f = max(f, n - s[i]); if (f < v) v = f, idx = i; } } memset(dist, 0, sizeof(dist)); memset(pre, 0, sizeof(pre)); pre[idx] = -1; dfs(idx); int ans = 0; rep(i,1,n) ans += dist[i]; cout << ans << '\n'; return 0; }
LCA
求法
- 先计算两个节点深度
- 调整同一深度
- 两个节点一起往上跳,直到两个节点相等
代码实现
小数据
时间复杂度\(O(n^2)\)
const int N = 1001; int n, m, father[N], dist[N]; vector<int> e[N]; il void dfs(int x){ for (auto y : e[x]) dist[y] = dist[x] + 1, dfs(y); } int main(void) { clock_t c1 = clock(); FastIO(); cin >> n; rep(i,1,n) { int x, y; cin >> x >> y; e[x].pb(y); father[y] = x; } memset(dist, 0, sizeof(dist)); dfs(1); cin >> m; rep(i,1,m) { int x, y; cin >> x >> y; if (dist[x] < dist[y]) swap(x, y); int z = dist[x] - dist[y]; rep(i,j,z) x = father[x]; while (x != y) x = father[x], y = father[y]; cout << x << '\n'; } cerr << "Time Used:" << clock() - c1 << "ms" << endl; return 0; }
大数据
时间复杂度\(O(logn)\)
int n, m, father[N], dist[N]; vector<int> e[N]; il void dfs(int x) { for (auto y : e[x]) dist[y] = dist[x] + 1, dfs(y); } int main(void) { clock_t c1 = clock(); FastIO(); cin >> n; rep(i,1,n) { int x, y; cin >> x >> y; e[x].pb(y); father[y][0] = x; } rep(i,1,20) rep(j,1,n) if (father[j][i - 1]) father[j][i] = father[father[j][i-1]][i-1]; memset(dist, 0, sizeof(dist)); dfs(1); cin >> m; rep(i,1,m) { int x, y; cin >> x >> y; if (dist[x] < dist[y]) swap(x, y); int z = dist[x] - dist[y]; for (int j = 0; j <= 20 && z; j++, z >>= 1) if (z & 1) x = father[x][j]; if (x == y) { cout << x << '\n'; continue; } per(j,20,0) if (father[x][j] != father[y][j]) x = father[x][j], y = father[y][j]; cout << father[x][0] << '\n'; } cerr << "Time Used:" << clock() - c1 << "ms" << endl; return 0; }
用欧拉序来求LCA
const int N = 501000, LOGN = 20; int n, q, p[N], tot, dep[N]; vector<int> e[N]; PII f[LOGN + 2][N * 2]; ll ans; unsigned int A, B, C; inline unsigned int rng61() { A ^= A << 16; A ^= A >> 5; A ^= A << 1; unsigned int t = A; A = B; B = C; C ^= t ^ A; return C; } void dfs(int u, int par) { p[u] = ++tot; dep[u] = dep[par] + 1; f[0][tot] = {dep[u], u}; for (auto v : e[u]) { if (v == par) continue; dfs(v, u); f[0][++tot] = {dep[u], u}; } } int main(){ scanf("%d%d%u%u%u", &n, &q, &A, &B, &C); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d",&u,&v); e[u].pb(v); e[v].pb(u); } dfs(1, 0); for (int j = 1; j <= LOGN; j++) for (int i = 1; i + (1 << j) - 1 <= tot; i++) f[j][i] = min(f[j-1][i], f[j - 1][i + (1 << (j - 1))]); for (int i = 1; i <= q; i++) { int l = p[rng61() % n + 1], r = p[rng61() % n + 1]; if (l > r) swap(l, r); int len = 31 - __builtin_clz(r - l + 1); int d = min(f[len][l], f[len][r - (1 << len) + 1]).second; ans ^= 1ll * i * d; } printf("%lld\n",ans); return 0; }
这篇关于倍增,DFS序,欧拉序和树的一些知识的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-13怎么通过 JavaScript 或其他编程语言来实现监听屏幕高度变化功能?-icode9专业技术文章分享
- 2024-11-12聊聊我们那些年用过的表达式引擎组件
- 2024-11-12让项目数据更有说服力:五款必备数据可视化管理工具推荐
- 2024-11-12人到一定年纪,要学会远离多巴胺
- 2024-11-12解读:精益生产管理的目的是什么?如何操作?
- 2024-11-12Sku预研作业
- 2024-11-12文心一言API密钥:分步申请指南
- 2024-11-12初学者指南:轻松掌握后台交互
- 2024-11-12从零开始学习:封装基础知识详解
- 2024-11-12JSON对象入门教程:轻松掌握基础用法