最近公共祖先

前置

1.向上标记法\(O(N)\)

2.倍增法

\(fa[i, j]\)从i开始向上走\(2^j\)步所能走到的节点。\(0 \le j \le log_{2}n\)

\(f[i, j] = f[f[i, j - 1], j - 1]\)

\(depth[]\)表示深度

二进制拼凑思想:找一个数的二进制,从高位往低位拼凑。

步骤

1.现将两个点跳到同一层。

2.让两个点同时往上面跳,一直跳到它们最近公共最先的下一层(父亲是最近公共祖先)。

预处理\(O(nlogn)\)
查询\(O(logn)\)

祖孙询问

#include <bits/stdc++.h>
using namespace std;
const int N = 40005, M = 40005;
int n, m, cnt = - 1, rt = 1, f[N][17], dep[N];
vector<int> g[N];
void build_tree(int x, int fa)
{
	f[x][0] = fa, dep[x] = dep[fa] + 1; 
	for (int i = 1; i <= 16; ++ i) 
	{
		f[x][i] = f[f[x][i - 1]][i - 1];
		if(!f[x][i]) break;
	}
	for (int i = 0; i < g[x].size(); ++ i)
	{
		int y = g[x][i];
		if(y == fa) continue;
		build_tree(y, x);
	}
	return ;
}
int lca(int x, int y)
{
	if(dep[x] < dep[y]) swap(x, y);
	for (int i = 16; i >= 0; -- i)
	{
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
	}
	if(x == y) return x;
	for (int i = 16; i >= 0; -- i)
	{
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; ++ i)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		if(v == -1) rt = u;
		else g[u].push_back(v), g[v].push_back(u);
	}
	build_tree(rt, 0);
	int q;
	scanf("%d", &q);
	while(q --)
	{
		int u, v;
		scanf("%d %d", &u ,&v);
		if(u == v) printf("0\n");
		else
		{
			int l = lca(u, v);
			if(l == u) printf("1\n");
			else if(l == v) printf("2\n");
			else printf("0\n");
		}
	}
	return 0;
}

距离

#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
int n, q;
struct edge
{
	int v, w;
};
vector<edge> g[N];
int f[N][17], dep[N], dis[N];
void build_tree(int u, int fa)
{
	dep[u] = dep[fa] + 1, f[u][0] = fa;
	for (int i = 1; i <= 16; ++ i) 
	{
		f[u][i] = f[f[u][i - 1]][i - 1];
		if(!f[u][i]) break;
	}
	for (int i = 0; i < g[u].size(); ++ i)
	{
		int v = g[u][i].v;
		if(v == fa) continue;
		dis[v] = dis[u] + g[u][i].w;
		build_tree(v, u);
	}
	return ;
}
int lca(int x, int y)
{
	if(dep[x] < dep[y]) swap(x, y);
	for (int i = 16; i >= 0; -- i)
	{
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
	}
	if(x == y) return x;
	for (int i = 16; i >= 0; -- i)
	{
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
int main()
{
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n - 1; ++ i)	
	{
		int u, v, w;
		scanf("%d %d %d", &u, &v, &w);
		g[u].push_back((edge){v, w});
		g[v].push_back((edge){u, w});
	}
	build_tree(1, 0);
	while(q --)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		int l = lca(u, v);
		printf("%d\n", dis[u] + dis[v] - 2 * dis[l]);
	}
}

次小生成树

定理:对于一张无向图,如果存在最小生成树和严格次小生成树,那么对于任何一棵最小生成树,都存在一个严格次小生成树,使得这两棵树只有一条边不同。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 300005, N = 100005;
int n, m, fd[N];
ll mit = 0;
bool vis[M];
struct edge1
{
	int u, v, w;
	friend bool operator < (edge1 a, edge1 b) {return a.w < b.w; }
}p[M];
struct edge2
{
	int v, w;
};
vector<edge2> g[N];
int findset(int x)
{
	if(x == fd[x]) return x;
	else return fd[x] = findset(fd[x]); 
}
void kruskal()
{
	for (int i = 1; i <= n; ++ i) fd[i] = i;
	sort(p + 1, p + m + 1);
	int sum = 0;
	for (int i = 1; i <= m; ++ i)
	{
		int u = findset(p[i].u), v = findset(p[i].v);
		if(u == v) continue;
		g[p[i].u].push_back((edge2){p[i].v, p[i].w});
		g[p[i].v].push_back((edge2){p[i].u, p[i].w});
		fd[u] = v, vis[i] = true, sum ++;
		mit += (ll)p[i].w;
		if(sum == n - 1) break;
	}
	return ;
}
int f[N][18], dep[N], d1[N][18], d2[N][18], val[10];
void build_tree(int x, int fa)
{
	dep[x] = dep[fa] + 1, f[x][0] = fa;
	for (int i = 1; i <= 17; ++ i) 
	{
		f[x][i] = f[f[x][i - 1]][i - 1];
		val[0] = d1[x][i - 1], val[1] = d1[f[x][i - 1]][i - 1], val[2] = d2[x][i - 1], val[3] = d2[f[x][i - 1]][i - 1];
		sort(val, val + 3 + 1);
		d1[x][i] = val[3];
		for (int j = 3; j >= 0; -- j) if(val[j] != val[3]) {d2[x][i] = val[j]; break;}
		if(!f[x][i]) break;
	}
	for (int i = 0; i < g[x].size(); ++ i)
	{
		int y = g[x][i].v, w = g[x][i].w;
		if(y == fa) continue;
		d1[y][0] = w;
 		build_tree(y, x);
	}
	return ;
}
int lca(int x, int y, int w)
{
	if(dep[x] < dep[y]) swap(x, y);
	int ans = 0;
	for (int i = 17; i >= 0; -- i)
	{
		if(dep[f[x][i]] >= dep[y]) 
		{
			if(w == d1[x][i]) ans = max(ans, d2[x][i]);
			else ans = max(ans, d1[x][i]);
			x = f[x][i];
		}
	}
	if(x == y) return ans;
	for (int i = 17; i >= 0; -- i)
	{
		if(f[x][i] != f[y][i])
		{
			if(w == d1[x][i]) ans = max(ans, d2[x][i]);
			else ans = max(ans, d1[x][i]);
			
			if(w == d1[y][i]) ans = max(ans, d2[y][i]);
			else ans = max(ans, d1[y][i]);
			x = f[x][i], y = f[y][i];
		}
	}
	if(w != d1[x][0]) ans = max(ans, d1[x][0]);
	if(w != d1[y][0]) ans = max(ans, d1[y][0]);
	return ans; 
}
int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; ++ i) scanf("%d %d %d", &p[i].u, &p[i].v, &p[i].w);
	kruskal();
	build_tree(1, 0);
	ll ans = 0x7f7f7f7f7f7f7f;
	for (int i = 1; i <= m; ++ i)
	{
		if(!vis[i] && p[i].u != p[i].v)
		{
			int vlu = lca(p[i].u, p[i].v, p[i].w);
			if(!vlu) continue;
			ans = min(ans, mit - (ll)vlu + p[i].w);
		}
	}
	printf("%lld", ans);
	return 0;
}

这代码已经很短了,汪氏打法。比网上大多数要短了。

闇の連鎖

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, val[N], ad[N], sum = 0;
vector<int> g[N];
int f[N][18], dep[N];
void build_tree(int x, int fa)
{
	f[x][0] = fa, dep[x] = dep[fa] + 1;
	for (int i = 1; i <= 17; ++ i) 
	{
		f[x][i] = f[f[x][i - 1]][i - 1];
		if(!f[x][i]) break;
	}
	for (int i = 0; i < g[x].size(); ++ i)
	{
		int y = g[x][i];
		if(y != fa) build_tree(y, x);
	}
	return ;
}
int lca(int x, int y)
{
	if(x == y) return x;
	if(dep[x] < dep[y]) swap(x, y);
	if(dep[x] != dep[y])
	{
		for (int i = 17; i >= 0; -- i)
		{
			if(dep[f[x][i]] >= dep[y]) x = f[x][i];
		}	
	}
	if(x == y) return x;
	for (int i = 17; i >= 0; -- i)
	{
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
void dfs(int x, int fa)
{
	for (int i = 0; i < g[x].size(); ++ i)
	{
		int y = g[x][i];
		if(y == fa) continue;
		dfs(y, x);
		val[x] += val[y];
	}
	return ;
	
}
int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n - 1; ++ i)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}	
	build_tree(1, 0);
	for (int i = 1; i <= m; ++i)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		int l = lca(u, v);
		val[u] ++, val[v] ++, val[l] -= 2;
	} 
	dfs(1, 0);
	for (int i = 2; i <= n; ++ i) 
	{
		if(!val[i]) sum += m;
		else if(val[i] == 1) sum ++;
	}
	printf("%d", sum);
	return 0;
}
posted @ 2025-02-13 10:39  Helioca  阅读(25)  评论(0)    收藏  举报
Document