acwing提高LCA
最近公共祖先(LCA,Lowest Common Ancestor)
1172. 祖孙询问(倍增LCA)

输入样例:
10
234 -1
12 234
13 234
14 234
15 234
16 234
17 234
18 234
19 234
233 19
5
234 233
233 12
233 13
233 15
233 19
输出样例:
1
0
0
0
2
代码模板
//倍增法 求 LCA
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 40010, M = N * 2;
int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][16];
int q[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//预处理
void bfs(int root)
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[root] = 1; //0 -> 哨兵
int hh = 0, tt = 0;
q[0] = root;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t;
for (int k = 1; k <= 15; 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);
//使 a 与 b 处于同一深度
for (int k = 15; k >= 0; k -- )
if (depth[fa[a][k]] >= depth[b])
a = fa[a][k];
if (a == b) return a;
//a 与 b 同时往上跳直到跳到最近公共祖先的下一个节点
for (int k = 15; k >= 0; k -- )
if (fa[a][k] != fa[b][k]) //核心 -> 跳到公共祖先之下, 因为跳过了最近公共祖先, if成立 0 == 0
{
a = fa[a][k];
b = fa[b][k];
}
return fa[a][0];
}
int main()
{
scanf("%d", &n);
int root = 0;
memset(h, -1, sizeof h);
//输入建图
for (int i = 0; i < n; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
if (b == -1) root = a;
else add(a, b), add(b, a);
}
//预处理 depth 和 fa 数组
bfs(root);
scanf("%d", &m);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
int p = lca(a, b);
if (p == a) puts("1");
else if (p == b) puts("2");
else puts("0");
}
return 0;
}
1171. 距离(Tarjan-离线求LCA)

输入样例:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例:
10
25
代码模板
//Tarjan-离线求LCA
//树的节点和节点的distance必定经过最近公共祖先(LCA)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 10010, M = N * 2;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int p[N];
int res[M];
int st[N];
vector<PII> query[N]; // first存查询的另外一个点,second存查询编号
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
//递归-预处理dist数组
void dfs(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
dist[j] = dist[u] + w[i];
dfs(j, u);
}
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void tarjan(int u)
{
st[u] = 1; //表示正在访问
//对u这条路上的根节点的左下的点用并查集合并到根节点
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!st[j]) // 0 表示还没有访问过
{
tarjan(j); // dfs搜到底
p[j] = u; //搜过的节点合并到根节点
}
}
for (auto item : query[u])
{
int y = item.first, id = item.second; // y节点 和节点编号
if (st[y] == 2) // 如果需要询问的点已经是左下的点
{
int anc = find(y); //获得询问点y的根节点, 即最近公共祖先
res[id] = dist[u] + dist[y] - dist[anc] * 2;
}
}
st[u] = 2; //访问结束并且已经回溯
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < m; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
if (a != b)
{
query[a].push_back({b, i});
query[b].push_back({a, i});
}
}
for (int i = 1; i <= n; i ++ ) p[i] = i;
dfs(1, -1); //任意节点为父节点
tarjan(1);
for (int i = 0; i < m; i ++ ) printf("%d\n", res[i]);
return 0;
}
356. 严格次小生成树(Kruskal + 倍增法LCA)

输入样例:
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
输出样例:
11
代码模板
//严格最小生成树 时间复杂度 O(mlogn)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 100010, M = 300010, INF = 0x3f3f3f3f;
typedef long long LL;
int n, m;
int h[N], ne[M], e[M], w[M], idx;
int p[N];
int fa[N][20], d1[N][20], d2[N][20], depth[N];
struct edges{
int a, b, w;
bool f;
bool operator < (const edges &e ) const
{
return w < e.w;
}
}ed[M];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
LL kruskal()
{
for(int i = 1; i <= n; i ++ ) p[i] = i;
sort(ed, ed + m);
LL res = 0;
for(int i = 0; i < m; i ++ )
{
int a = ed[i].a, b = ed[i].b, c = ed[i].w;
a = find(a), b= find(b);
if(a != b)
{
p[a] = b;
ed[i].f = true;
res += c;
}
}
return res;
}
void built()
{
for(int i = 0; i < m; i ++ )
{
if(ed[i].f)
{
int a = ed[i].a, b = ed[i].b, c = ed[i].w;
add(a, b, c), add(b, a, c);
}
}
}
/*
预处理数组
depth[i] 节点 i 的深度
对 depth bfs同时可以求得
fa[i][j] 从i开始跳 2^j步的节点
d1[i][j] 节点 i 开始跳 2^j步范围内的最大值
d2[i][j] 节点 i开始跳 2^j 步范围内的次大值
*/
void bfs()
{
memset(depth, 0x3f, sizeof depth);
queue<int> q;
q.push(1); //取树种任意节点作为父节点
depth[1] = 1, depth[0] = 0; // 0 -> 哨兵
while(q.size())
{
int t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if(depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q.push(j);
fa[j][0] = t; // j的父节点为 t
d1[j][0] = w[i], d2[j][0] = -INF; //d1:j到父节点的最大值 d2:j到父节点的次大值
for(int k = 1; k <= 16; k ++ ) //倍增法 倍增范围内的最大值和次大值d1,d2
{
int anc = fa[j][k - 1];
fa[j][k] = fa[anc][k - 1];
int distance[4] = {d1[j][k - 1], d1[anc][k - 1], d2[j][k - 1], d2[anc][k - 1]};
for(int u = 0; u < 4; u ++ )
{
int x = distance[u];
if(x > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = x;
else if(x > d2[j][k] && x != d1[j][k]) d2[j][k] = x;
}
}
}
}
}
}
//用非树边代替 a - b 的最大值或者次大值
int loc(int a, int b, int w)
{
//倍增查询,查询过程种记录路径中的最大值和次大值
static int distance[N * 2];
int cnt = 0;
if(depth[a] < depth[b]) swap(a, b);
//使 a 和 b 处于同一深度
for(int k = 16; k >= 0; k -- ) //可以抽象成二进制位
{
if(depth[fa[a][k]] >= depth[b])
{
distance[cnt ++] = d1[a][k];
distance[cnt ++] = d2[a][k];
a = fa[a][k];
}
}
//a 和 b 同时向上跳 直到 a == b即找到公共祖节点
if(a != b)
{
for(int k = 16; k >=0; k -- )
{
if(fa[a][k] != fa[b][k]) // 注意: 如果跳过了公共祖节点会都等于0 if不成立
{
distance[cnt ++] = d1[a][k];
distance[cnt ++] = d2[a][k];
distance[cnt ++] = d1[b][k];
distance[cnt ++] = d2[b][k];
a = fa[a][k], b = fa[b][k];
}
distance[cnt ++ ] = d1[a][0];
distance[cnt ++ ] = d1[b][0];
}
}
int dist1 = 0, dist2 = 0;
//重新整理得到 a - b 的最大值和次大值
for(int i = 0; i < cnt; i ++ )
{
if(distance[i] > dist1) dist2 = dist1, dist1 = distance[i];
else if (distance[i] != dist1 && distance[i] > dist2) dist2 = distance[i];
}
if(w > dist1) return w - dist1;
if(w > dist2) return w - dist2;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++ )
{
int a, b, w;
cin >> a >> b >> w;
ed[i] = {a, b, w};
}
LL res = kruskal(); //得到最小生成树
built(); //最小生成树建图
bfs(); //预处理数组fa, d1, d2, loc;
LL sum = 1e18;
//枚举所有非树边
for(int i = 0; i < m; i ++ )
{
if(!ed[i].f)
{
sum = min(sum, res + loc(ed[i].a, ed[i].b, ed[i].w));
}
}
cout << sum << endl;
return 0;
}
输入样例:
输出样例:
代码模板

浙公网安备 33010602011771号