这道题还是比较经典的,涉及的知识点还是很多的。看到很多大佬都是用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