洛谷 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,活久见...

posted @ 2021-05-19 21:51  Paranoid5  阅读(82)  评论(0编辑  收藏  举报