LCA 最近公共祖先
LCA 树上倍增法
实质是把节点每次向上暴力移动一步变成更加高明的按 fa,fa[i][k] 代表 fa[fa[i][k - 1]][k - 1] 来递归更新。
初始化时使用宽搜,把所有点的深度更新一遍。注意要设置一个类似于
对于
模板:
#include <bits/stdc++.h>#define N 500010using namespace std;
struct Edge { int to, ne;} edges[N << 1];int h[N], idx = 0;
int fa[N][22];int dep[N];
int root = 0;
void add(int a, int b) { idx++; edges[idx].to = b; edges[idx].ne = h[a]; h[a] = idx;}
void init() { memset(dep, 0x3f, sizeof dep); queue<int> q; dep[0] = 0; dep[root] = 1; q.push(root); while (!q.empty()) { int t = q.front(); q.pop(); for (int i = h[t]; ~i; i = edges[i].ne) { int j = edges[i].to; if (dep[j] > dep[t] + 1) { dep[j] = dep[t] + 1; fa[j][0] = t; q.push(j); for (int k = 1; k <= 21; k++) { fa[j][k] = fa[fa[j][k - 1]][k - 1]; } } } }}
int LCA(int a, int b) { if (dep[a] < dep[b]) swap(a, b); for (int k = 21; k >= 0; k--) { if (dep[fa[a][k]] >= dep[b]) a = fa[a][k]; } if (a == b) return a; for (int k = 21; k >= 0; k--) { if (fa[a][k] != fa[b][k]) { a = fa[a][k]; b = fa[b][k]; } } return fa[a][0];}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
memset(h, -1, sizeof h); int n, m; cin >> n >> m >> root; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; add(x, y); add(y, x); } init(); while (m--) { int a, b; cin >> a >> b; cout << LCA(a, b) << endl; } return 0;}再探次小生成树
一个半月前,我写了次小生成树的求解方法,当时是将非树边挨个加入进树中,形成一个环,并剔除这个环中最大的树内边,比较这些操作对最小生成树总边权的贡献从而得到原树的非严格次小生成树(若需要严格次小生成树则需要维护环上次大边)。其中为了找到环内最/次大边,我们使用了指数级别的
洛谷 P4180 [BJWC2010] 严格次小生成树
题目地址:P4180
题目难度:提高+/省选-
题目来源:北京 2010 各省省选
依然是先求出最小生成树以及它的总边权,但是对于最大/次大边权的查询,考虑使用倍增
联系到递归求解
- 如果当前值严格大于维护的最大值,更新最大值为当前值,把次大值赋为原先的最大值。
- 如果当前值严格小于最大值且严格大于次大值,那么更新次大值为当前值。
对走两步分别得到的四个最/次大值执行遍历,就可以得到全局最/次大值了。在倍增
其中 d1[a][k] 代表节点 d2[a][k] 则是相应的次大权值。
维护树上差分
AcWing 352. 闇の連鎖
题目地址:AcWing 352
题目难度:困难
传说中的暗之连锁被人们称为 Dark。
Dark 是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它。
经过研究,你发现 Dark 呈现无向图的结构,图中有
个节点和两类边,一类边被称为主要边,而另一类被称为附加边。 Dark 有
条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。 另外,Dark 还有
条附加边。 你的任务是把 Dark 斩为不连通的两部分。
一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。
一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。
但是你的能力只能再切断 Dark 的一条附加边。
现在你想要知道,一共有多少种方案可以击败 Dark。
注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。
输入格式:
第一行包含两个整数
和 。 之后
行,每行包括两个整数 和 ,表示 和 之间有一条主要边。 之后
行以同样的格式给出附加边。 输出格式:
输出一个整数表示答案。
数据范围:
, ,数据保证答案不超过 。

假如树内边(主要边)用黑色表示,非树边(附加边)用红色表示。边上的数字代表:在砍断当前边的情况下还需要砍断多少附加边才能将图分成两部分(默认
- 当该边计数为
时,砍断当前边后,任意砍断一条附加边即可,答案累加 - 当该边计数为
时,砍断当前边后,只能砍断特定的一条附加边,答案累加 - 当该边计数大于
时,因为最多只能砍两刀,因此不能通过砍当前主要边击败 Dark,答案不变
如何更新边上的计数呢?运用树上差分的思想——把权值的差分存储在图上,和序列差分一样,如果想要给某个区间内所有的数加减某个数,则只需在左端点和右端点加/减这个数,然后右端点的右边再减/加这个数,最后求前缀和即可得到原数;树上差分也很类似,我们在附加边连接的两个点上更改数值,然后找到它们的最近公共祖先,减去两倍的值即可。
#include <bits/stdc++.h>#define N 100010using namespace std;
struct Edge { int to, ne;} edges[N << 1];int h[N], idx = 0;int fa[N][20], depth[N];int dist[N];int m, ans = 0;
void add(int a, int b) { idx++; edges[idx].to = b; edges[idx].ne = h[a]; h[a] = idx;}
void init(int root) { memset(depth, 0x3f, sizeof depth);
queue<int> q; depth[0] = 0; depth[root] = 1; q.push(1); while (!q.empty()) { int t = q.front(); q.pop(); for (int i = h[t]; ~i; i = edges[i].ne) { int j = edges[i].to; if (depth[j] > depth[t] + 1) { depth[j] = depth[t] + 1; fa[j][0] = t; q.push(j); for (int k = 1; k <= 19; k++) { fa[j][k] = fa[fa[j][k - 1]][k - 1]; } } } }}
int LCA(int a, int b) { if (depth[a] < depth[b]) swap(a, b); for (int k = 19; k >= 0; k--) { if (depth[fa[a][k]] >= depth[b]) a = fa[a][k]; } if (a == b) return a; for (int k = 19; k >= 0; k--) { if (fa[a][k] != fa[b][k]) { a = fa[a][k]; b = fa[b][k]; } } return fa[a][0];}
int dfs(int u, int f) { int res = dist[u]; // 当前记录的差分权 for (int i = h[u]; ~i; i = edges[i].ne) { int j = edges[i].to; if (j == f) continue; int t = dfs(j, u); // 获得子树的差分权值 if (t == 0) ans += m; else if (t == 1) ans++; res += t; // 向上更新,累加子树差分权 } return res;}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
memset(h, -1, sizeof h);
int n; cin >> n >> m; for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; add(a, b); add(b, a); } init(1); for (int i = 1; i <= m; i++) { int a, b; cin >> a >> b; int p = LCA(a, b); dist[a]++, dist[b]++, dist[p] -= 2; // 维护树上差分 } dfs(1, -1); cout << ans << endl; return 0;}支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
部分内容可能已过时