货车运输(kruskal+最大生成树+LCA)

题目链接:货车运输-洛谷

这道题还是比较经典的,涉及的知识点还是很多的。看到很多大佬都是用kruskal重构树+倍增LCA,奈何自己太菜,没有想到在重构树的过程中也可以维护答案,不过没关系!AC了就行

为什么要生成最大生成树,因为题中要求找两点所有路径中某一条路径的最小值的最大值,那么这个最大值所在的某一个路径肯定在最大生成树中(图论涉及最值,次最值,都可以考虑最小/大生成树,去掉无用边的干扰,将图转化为易处理的树)

需要解决的难题如下:

1.题目中说有多重边(刚开始没看到====唉。泪崩555),直接使用set去重,多重边就选最大的,因为题中说的是尽可能使货车下限更高

2.很容易想到kruskal+并查集,将图转为最大生成树,最大生成树,可以保证任意两点之间的路径的边的权值最大,再小的边也无需考虑,按边权值降序排序,然后kruskal做n-1次,生成最大生成树

3.接下来就是离线的进行查询,如果两个点不在一个连通块,直接输出-1(用并查集判断即可)。否则1,经典问题查找树上两点之间的路径,找到路径上最小权值即为每次询问的答案。这里要注意的是,题中图不一定是连通的,因此最大生成树求完,整个图变为森林,因此需要对每个树找一个根节点,我使用无效的结点0来代替。!!!然后,树求路径用dfs肯定会寄,交了一发,不出意料的tle了。换朴素的LCA,虽然LCA慢,但这道题LCA并不超时(也可以尝试倍增的LCA)。这里给出两种LCA的板子:

朴素的LCA

%先求每个点的深度
void dfs1(int u,int deep)
{
	dep[u]=deep;
	visit[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		if(!visit[g[u][i]])
		{
			dfs1(g[u][i],deep+1);
		}
	}
}
%再lca
int lca(int u,int v)
{
	int ans=0x3f3f3f3f;
	if(dep[u]!=dep[v])//比较两点的深度,选择点较深的先爬,爬到和另一个相同的高度,两者开始同时爬
	{
		if(dep[u]<dep[v])
		swap(u,v);
		while(u)
		{
			ans=min(ans,vi[u][father[u]]);
			u=father[u];
			if(dep[u]==dep[v])
			break;
		}
	}
	while(u!=v)//同时开始往上回溯
	{
		ans=min(ans,vi[u][father[u]]);
		ans=min(ans,vi[v][father[v]]);
		u=father[u];
		v=father[v];
	}
	return ans;
}

倍增的LCA,网上找的板子(OI-WIKI)

// dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。
void dfs(int root, int fno) {
  // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
  fa[root][0] = fno;
  dep[root] = dep[fa[root][0]] + 1;
  // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
  // 2^(i-1) 的祖先节点。
  for (int i = 1; i < 31; ++i) {
    fa[root][i] = fa[fa[root][i - 1]][i - 1];
    cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1];
  }
  // 遍历子节点来进行 dfs。
  int sz = v[root].size();
  for (int i = 0; i < sz; ++i) {
    if (v[root][i] == fno) continue;
    cost[v[root][i]][0] = w[root][i];
    dfs(v[root][i], root);
  }
}

// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lca(int x, int y) {
  // 令 y 比 x 深。
  if (dep[x] > dep[y]) swap(x, y);
  // 令 y 和 x 在一个深度。
  int tmp = dep[y] - dep[x], ans = 0;
  for (int j = 0; tmp; ++j, tmp >>= 1)
    if (tmp & 1) ans += cost[y][j], y = fa[y][j];
  // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
  if (y == x) return ans;
  // 不然的话,找到第一个不是它们祖先的两个点。
  for (int j = 30; j >= 0 && y != x; --j) {
    if (fa[x][j] != fa[y][j]) {
      ans += cost[x][j] + cost[y][j];
      x = fa[x][j];
      y = fa[y][j];
    }
  }
  // 返回结果。
  ans += cost[x][0] + cost[y][0];
  return ans;
}

完整代码如下:


#include<bits/stdc++.h>
using namespace std;
long long ma= 0x3f3f3f3f;
int fa[10005];
vector<int>g[10005];
int fl=0;
int dep[10004];
map<int,map<int,int>>vi;
int father[10004];
int visit[10004];
void dfs1(int u,int deep)//预处理计算深度,用于lca
{
	dep[u]=deep;
	visit[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		if(!visit[g[u][i]])
		{
			dfs1(g[u][i],deep+1);
		}
	}
}
void dfs(int u)//遍历树,预处理每个结点的父节点
{
	visit[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		if(!visit[g[u][i]])
		{
			father[g[u][i]]=u;
			dfs(g[u][i]);
		}
	}
}
int lca(int u,int v)
{
	int ans=0x3f3f3f3f;
	if(dep[u]!=dep[v])
	{
		if(dep[u]<dep[v])
		swap(u,v);
		while(u)
		{
			ans=min(ans,vi[u][father[u]]);
			u=father[u];
			if(dep[u]==dep[v])
			break;
		}
	}
	while(u!=v)
	{
		ans=min(ans,vi[u][father[u]]);
		ans=min(ans,vi[v][father[v]]);
		u=father[u];
		v=father[v];
	}
	return ans;
}
int find(int leaf)//并查集,老选手了
{
	if(leaf!=fa[leaf])
	{
		fa[leaf]=find(fa[leaf]);
	}
	return fa[leaf];
}
struct point{
	int u;
	int v;
	int z;
};
bool cmp(point &a,point&b)
{
	return a.z>b.z;
}

void solve()
{
	int n,m;
	cin>>n>>m;
	int u,v,z;
	for(int i=1;i<=n;i++)
		fa[i]=i;
	vector<point>p;
	set<pair<int,int>>nn;
	for(int i=0;i<m;i++)
	{
		cin>>u>>v>>z;
		if(v>u)
		{
			swap(u,v);
		}
		nn.insert({u,v});
		if(vi[u][v])
		{
			vi[u][v]=max(vi[u][v],z);
			vi[v][u]=max(vi[v][u],z);
		}
		else
		{
			vi[u][v]=z;
			vi[v][u]=z;
		
		}
		
	}
	for(auto x:nn)//去多重边
	{
		p.push_back({x.first,x.second,vi[x.first][x.second]});
	}
	sort(p.begin(),p.end(),cmp);
	int cnt=0;
	for(int i=0;i<m;i++)//kruskal求最大生成树
	{
		u=p[i].u;
		v=p[i].v;
		int f1=find(u);
		int f2=find(v);
		if(f1!=f2)
		{
			fa[f1]=f2;
			g[u].push_back(v);
			g[v].push_back(u);
				cnt++;
		}
	
		if(cnt==n-1)
		break;
	}
	for(int i=1;i<=n;i++)//处理各个连通块
	{	
		if(visit[i]==0)
		{
			father[i]=0;
			visit[i]=1;
			dfs(i);
		}
	}
	memset(visit,0,sizeof(visit));
	for(int i=1;i<=n;i++)//处理各个连通块
	{
		if(visit[i]==0)
		{
			visit[i]=1;
			dfs1(i,0);
		}
	}
	int q;
	cin>>q;
	for(int i=0;i<q;i++)
	{
		cin>>u>>v;
		if(find(u)!=find(v))
		cout<<"-1\n";
		else
		{
			cout<<lca(u,v)<<endl;
		}
	}
}
int main()
{
	solve();
}

ok!!!!!结束!!!别急————————————————

最后,讲解一下kruskal重构树的问题:

基本思想:在kruskal求最大生成树的时候,也可以边生成,边建树。比如,在处理u,v这两个点的时候,假设他们边权值为w,那么将边权值转化为点权值。即本来应该在u,v之间连一条边,但是咱们可以直接在两者之间添加一个权值为w的点x,并生成x-u,x-v两条边。这样就可以在LCA的最近点的点权值就是u,v路径上的最小值。

正确性:它们一开始不相连,在这一步后代表元素(两个根结点)连接权值为 w的点,才使得它们相连,所以最近公共祖先权值是新结点,权值 w。而在更后面的连边中,又不会更改它们的最近公共祖先的值

做法:新建一个节点x,将find(u)和find(v)的父亲改为x,并赋予x一个权值w

板子如下

        f1 = find(u);
        f2 = find(v);

        if(f1 == f2) continue;

        p[f1] = ++cnt;
        p[f2] = cnt;

        value.push_back(weight);

        parent[f1] = cnt;
        parent[f2] = cnt;

下机!!!——————————————————————————————qwq

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Never ♚ Back Down♟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值