洛谷 P1396(最小瓶颈树)
洛谷P1396
1.最小生成树写法
这道题在最小生成树的提单里,联想了一下,感觉就是Kruskal生成树上再跑一遍dfs找到最长边。后来才发现这是一个典型的最小瓶颈树(刚巧下午看蓝书看到这个)。
上板子Kruskal一发过了。
下面是我个人的ac代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e4+50;
ll n,m,cnt = 0;
struct Edge{
ll from,to,w;
bool operator < (const Edge &x)const{return x.w > w;}
}edge[N];
vector<Edge> G[N];
void add(int u,int v,int w){
G[u].push_back({u,v,w});
}
ll f[N] = {0};
ll find(ll x){
return x == f[x]?x:f[x] = find(f[x]);
}
void _union(ll x,ll y){
x = find(x),y = find(y);
if(x != y) f[x] = y;
}
ll ans = 0,tot = 0;
void Kruskal(){
for(int i = 1;i <= n;i++) f[i] = i;
sort(edge+1,edge+m+1);
ll ans = 0;
for(int i = 1;i <= m;i++){
ll u = edge[i].from,v = edge[i].to;
if(find(u) != find(v)){
ans += edge[i].w;
add(u,v,edge[i].w);
add(v,u,edge[i].w);
_union(u,v);
}
}
}
int s,t;
void dfs(int u,int fa,ll _max){
if(u == t) cout<<_max<<endl;
for(auto e:G[u]){
if(e.to != fa){
dfs(e.to,u,max(_max,e.w));
}
}
}
int main() {
cin>>n>>m>>s>>t;
for(int i = 1;i <= m;i++){
cin>>edge[i].from>>edge[i].to>>edge[i].w;
}
Kruskal();
dfs(s,0,-1);
return 0;
}
实际上这里有一个更好的解法(就是在我的思路上的一个优化),Kruskal在建树的时候,它满足从小到大升序加边,也就是说,在MST的一条路径中最后加进来的边就一定是最大的。
所以只需要在Kruskal算法中,简单加上一句
\(if(find(u) == find(v)) return\ w;\)即可。
(这里就不写了)
2.最短路写法(DP写法)
看到博客中有很多大佬都用了最短路算法来求这个问题,去看了一手。
对于SPFA的写法,其实我认为这是一种DP。
SPFA的原型算法就是bellman-ford,这是一种DP的方法,状态转移为
\(d[v] = min(d[v],d[u]+edge\{u,v\})\)
现在改成了\(d[v] = max(d[u],edeg\{u,v\})\)。这就是DP或者最短路的写法,当然,还有人用dijkstra算法,原理是一样的。
```cpp
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e4+50;
const int inf = 0x3f3f3f3f;
int n,m,s,t;//点数,边数,起点,终点
struct Edge{
int u,v;
int w;
Edge(int _u = 0,int _v = 0,int _w = 0):u(_u),v(_v),w(_w){}
};
vector<Edge> G[N];
void add(int u,int v,int w){
G[u].push_back(Edge(u,v,w));
}
bool vis[N];//检测是否在队列中
int cnt[N],dis[N];//cnt表示进入队列次数,dis表示距离
queue<int>q;
void spfa(){
memset(vis,false,sizeof(vis));
for(int i = 1;i <= n;i++)dis[i] = inf;
vis[s] = true;dis[s] = 0;
while(!q.empty()) q.pop();
q.push(s);
memset(cnt,0,sizeof cnt);
cnt[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();vis[u] = false;
for(auto e:G[u]){
int v = e.v;
if(dis[v] > max(dis[u],e.w)){
dis[v] = max(dis[u],e.w);
if(!vis[v]){
vis[v] = true;
q.push(v);
//因为存在一个点被访问了超过n次所以该回路存在负环回路
}
}
}
}
}
int main() {
cin>>n>>m>>s>>t;
for(int i = 1;i <= m;i++){
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
spfa();
cout<<dis[t]<<endl;
return 0;
}
最后还看到有人用二分来写这个问题。最小值最大问题确实容易让人认为这是一个二分答案的问题。
思路就是,二分最大拥挤度,看这个拥挤度可不可以让s和t联通。
还有人用lca,活久见...