前言
传送门:
引用站外地址
这道题算是一个稍有思维难度的 MST+LCA 题目了。
稍微卡了一会(0-88-88-88-100(打表)-100(打表)-100(正解)),开始是打了表过了,后面在同学的帮助下正解通过(下面注释提到的一个坑)。
题目大意
给出一张无向图 G,有 n 个点和 m 个边 (x,y)=z ,找到一条路径使 (u→v) 中最小的 w 最大并输出 w,如果找不到这样的路径,输出 -1
。
题目解析
考虑最大生成树,因为最大生成树可以保证图的每个点之间依然联通且可以拿到尽可能大的边权,可以保证接下来能操作的路径之间权值更大。
这里使用 Kruskal 算法,在执行时以每条当前极大边建无向图就行了。
得到这个树形结构的图之后,为了获取两点之间的通路和最小的 w ,就需要求解 LCA(最近公共祖先)了,下面采用倍增的方式。
如果直接采用普通的 ST LCA,会发现一个问题:在求解时最小的 w 怎么获取呢?
我们可以考虑在执行预处理 pre()
时和 f[u][k]
同时维护一个数组 wi[u][k]
表示点 u 向上走 2k 步时最小的 w。
像 f(u, i+1)=f(f(u, i), i) 这个式子一样,易得 wi(u, i+1)=min(wi(u, i),wi(f(u, i), i))
为了在 pre()
中能够处理 wi[][]
,我们需要在 pre(u,fa)
新增一个参数 w
,表示 (fa,u) 的权值,以此来设置边界条件,即:wi(u, 0)=w
最后在 lca()
中维护一个 ans 表示 (u→v) 中最小的 w,并在每次上移时更新 ans,返回该值即可。
还有一个问题:如何判定两点不通?其实在运行 Kruskal 时留下的并查集就可以发挥作用,如果两点不通,肯定不是在一个集合里面的,所以只需要在 lca()
中的第一行这样子写:
1
| if(find(x)!=find(y)) return -1;
|
代码
下面的代码注释会提出几个小坑,如果您自己码题时不注意您一定无法通过此题(打表除外)!
注释版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <bits/stdc++.h> using namespace std; const int maxn=5e5+5; struct r {int u,v,w;}; int n,m,cnt,dep[maxn],fa[maxn],f[maxn][24],wi[maxn][24],vis[maxn]; vector<pair<int,int>> g[maxn]; vector<r> edge; inline void add(int u,int v,int w) {g[u].push_back(make_pair(v,w));} inline int find(int x) {return x==fa[x]?x:fa[x]=find(fa[x]);} void pre(int u,int fa,int w){ dep[u]=dep[fa]+1; f[u][0]=fa,wi[u][0]=w,vis[u]=1; for(int i=0;i<20;i++) f[u][i+1]=f[f[u][i]][i], wi[u][i+1]=min(wi[u][i],wi[f[u][i]][i]); for(auto [v,w]:g[u]) if(!vis[v]) pre(v,u,w); } int lca(int x,int y){ int ans=1e9; if(find(x)!=find(y)) return -1; if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--){ if(dep[f[x][i]]>=dep[y]) ans=min(ans,wi[x][i]),x=f[x][i]; if(x==y) return ans; } for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) ans=min(ans,min(wi[x][i],wi[y][i])), x=f[x][i],y=f[y][i]; return min(ans,min(wi[x][0],wi[y][0])); } inline void kruskal(){ sort(edge.begin(),edge.end(),[](r a,r b){ return a.w>b.w; }); for(auto [u,v,w]:edge){ int x=find(u),y=find(v); if(x==y) continue; fa[x]=y,cnt++; add(u,v,w),add(v,u,w); if(cnt==n-1) break; } } int main(){ ios::sync_with_stdio(0); cin>>n>>m; for(int i=1,a,b,w;i<=m;i++) cin>>a>>b>>w,edge.push_back({a,b,w}); iota(fa+1,fa+1+n,1),kruskal(); pre(1,0,0);int x,y,q;cin>>q; while(q--){ cin>>x>>y; if(!vis[x]) pre(x,0,0); cout<<lca(x,y)<<endl; } return 0; }
|
极速版
可以CTJ的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <bits/stdc++.h> using namespace std; const int maxn=5e5+5; struct r {int u,v,w;}; int n,m,cnt,dep[maxn],fa[maxn],f[maxn][24],wi[maxn][24],vis[maxn]; vector<pair<int,int>> g[maxn]; vector<r> edge; inline void add(int u,int v,int w) {g[u].push_back(make_pair(v,w));} inline int find(int x) {return x==fa[x]?x:fa[x]=find(fa[x]);} void pre(int u,int fa,int w){ dep[u]=dep[fa]+1; f[u][0]=fa,wi[u][0]=w,vis[u]=1; for(int i=0;i<20;i++) f[u][i+1]=f[f[u][i]][i], wi[u][i+1]=min(wi[u][i],wi[f[u][i]][i]); for(auto [v,w]:g[u]) if(!vis[v]) pre(v,u,w); } int lca(int x,int y){ int ans=1e9; if(find(x)!=find(y)) return -1; if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--){ if(dep[f[x][i]]>=dep[y]) ans=min(ans,wi[x][i]),x=f[x][i]; if(x==y) return ans; } for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) ans=min(ans,min(wi[x][i],wi[y][i])), x=f[x][i],y=f[y][i]; return min(ans,min(wi[x][0],wi[y][0])); } inline void kruskal(){ sort(edge.begin(),edge.end(),[](r a,r b){ return a.w>b.w; }); for(auto [u,v,w]:edge){ int x=find(u),y=find(v); if(x==y) continue; fa[x]=y,cnt++; add(u,v,w),add(v,u,w); if(cnt==n-1) break; } } int main(){ ios::sync_with_stdio(0); cin>>n>>m; for(int i=1,a,b,w;i<=m;i++) cin>>a>>b>>w,edge.push_back({a,b,w}); iota(fa+1,fa+1+n,1),kruskal(); pre(1,0,0);int x,y,q;cin>>q; while(q--){ cin>>x>>y; if(!vis[x]) pre(x,0,0); cout<<lca(x,y)<<endl; } return 0; }
|