题解 P3366 【【模板】最小生成树】

蒟蒻的我的题解。如有错误,还望指正。题目链接:洛谷P3366

Kruskal

本质是并查集加贪心。

不清楚并查集可以戳这

用时: 268ms / 内存: 2988KB

要让n个点连通,那么至少选n-1条边,又要让各边的长度之和最小,那么只要每次选出最短的边即可,但是注意已经联通的点无须再增加多余的边,这个可以用并查集判断,是否在一个集合内。


#include
#define maxn 200005
using namespace std;
struct node{
    int u;
    int v;
    int w;
}edge[maxn];
bool cmp(node a, node b){//按边权值排序
    return a.w < b.w;
}
int sum, n, m, ans;
int dad[maxn];
void init(){//初始化
    for(int i = 1 ; i <= n ; ++ i){
        dad[i] = i;
    }
}
int find_dad(int i){//递归状压找祖宗
    if(dad[i] == i)
        return i;
    else
        return dad[i] = find_dad(dad[i]);
}
int hebin(int x, int y){//合并
    int zx = find_dad(x);
    int zy = find_dad(y);
    if(zx != zy){
        dad[zy] = zx;
        return 1;
    }
    return 0;
}
int main()
{
    scanf("%d %d", &n, &m);
    init();
    for(int i = 1 ; i <= m ; ++ i){
        scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w);
    }
    sort(edge + 1, edge + 1 + m, cmp);
    for(int i = 1 ; i <= m ; ++ i){
        if(hebin(edge[i].u, edge[i].v)){//如果不在一个集合,合并
            sum ++;
            ans += edge[i].w;
        }
        if(sum == n - 1) break;//选出n-1条边退出
    }
    cout<<ans;
    return 0;
}

不用堆优化的prime+链式前向星存图

想了解链式前向星的戳这里

用时: 578ms / 内存: 5492KB

prime和迪杰斯特拉很像,只不过每次松弛的时候,从dis数组里选出离生成树最近的顶点j,加入生成树中,再以j为中间点更新生成树到每一个非树顶点的距离。

至于找出最近的定点j可以用堆来优化,降低复杂度,使用堆优化的方法在下面。

#include
#define INF 0x3f3f3f3f
#define maxn 200005
#define maxm 5005
using namespace std;
struct node{
    int to;
    int quan;
    int next;
}edge[maxn << 1];//双向边开两倍
int n, m, sum, ans, cnt;
int head[maxm], book[maxm], dis[maxm];
void add(int u, int v, int w){//链式前向星存图
    edge[cnt].to = v;
    edge[cnt].quan = w;
    edge[cnt].next = head[u];
    head[u] = cnt ++;
}
int main()
{
    int u, v, w;
    memset(head, -1, sizeof(head));
    scanf("%d %d", &n, &m);
    for(int i = 1 ; i <= n ; ++ i) dis[i] = INF;
    for(int i = 1 ; i <= m ; ++ i){
        scanf("%d %d %d", &u, &v, &w);
        add(u, v, w); add(v, u, w);//双向加边
    }
    for(int i = head[1] ; i != -1 ; i = edge[i].next){
        int tt = edge[i].to;
        dis[tt] = min(dis[tt], edge[i].quan);
    }
    book[1] = 1;
    sum ++;
    while(sum < n){
        int mini = INF, t = -1;
        for(int i = 1 ; i <= n ; ++ i){
            if(dis[i] < mini && !book[i]){
                mini = dis[i];
                t = i;
            }
        }
        book[t] = 1;
        sum ++; ans += dis[t];
        for(int i = head[t] ; i != -1 ; i = edge[i].next){
            int tt = edge[i].to;
            if(dis[tt] > edge[i].quan && !book[tt]){
                dis[tt] = edge[i].quan;
            }
        }
    }
    cout<<ans;
    return 0;
}

用了手写堆优化prime+链式前向星存图

用时: 196ms / 内存: 5420KB

#include
#define INF 0x3f3f3f3f
#define maxn 200005
#define maxm 5005
using namespace std;
struct node{
    int to;
    int quan;
    int next;
}edge[maxn << 1];
int n, m, sum, ans, cnt;
int head[maxm], book[maxm], dis[maxm];
int h[maxm], pos[maxm], siz;
void add(int u, int v, int w){
    edge[cnt].to = v;
    edge[cnt].quan = w;
    edge[cnt].next = head[u];
    head[u] = cnt ++;
}
void init(){
    siz = n;
    for(int i = 1 ; i <= siz ; ++ i){
        pos[i] = i; h[i] = i;
    }
}
void siftdown(int i){//传入一个需要向下调整的节点编号i,即从编号为i的点向下调整
    int flag = 0, t;//flag代表是否还需要向下调整
    while(i * 2 <= siz && !flag){//只要i节点有左儿子,并且需要继续向下调整就执行
        if(dis[h[i]] > dis[h[i * 2]])//判断与左儿子大小,t记录较小节点编号
            t = i * 2;
        else
            t = i;
        if(i * 2 + 1 <= siz){//如果它还有右儿子,就继续比较,也记录小的节点的编号
            if(dis[h[i * 2 + 1]] < dis[h[t]])
                t = i * 2 + 1;
        }
        if(t != i){//如果最小节点编号不是自己,说明子节点有比自己还小的
            swap(h[t], h[i]);
            swap(pos[h[t]], pos[h[i]]);
            i = t;//更新i为与它交换节点的编号。
        }
        else flag = 1;//否则说明当前节点已经比他的子节点小,无需调整
    }
}
void siftup(int i){//传入一个需要向上调整的节点编号
    int flag = 0;//flag代表是否还需要向上调整
    if(i == 1) return;//如果是堆顶,就返回,无需调整
    while(i != 1 && !flag){//不是堆顶并且i节点的值比父节点小就向上调整
        if(dis[h[i]] < dis[h[i / 2]]){//判断是否小于父节点
            swap(h[i], h[i / 2]);//交换
            swap(pos[h[i]], pos[h[i / 2]]);
        }
        else flag = 1;//当前节点比父节点大了就无需调整
        i /= 2;//更新i的编号
    }
}
void heap(){//建立小根堆
    for(int i = siz / 2 ; i >= 1 ; i --){
        siftdown(i);
    }
}
int pop(){
    int t;
    t = h[1];
    pos[t] = 0;
    h[1] = h[siz];
    pos[h[1]] = 1;
    siz --;
    siftdown(1);
    return t;
}
void prime()
{
    book[1] = 1;
    sum ++;
    while(sum < n){
        int mini = INF, t = -1;
        t = pop();
        book[t] = 1;
        sum ++; ans += dis[t];
        for(int i = head[t] ; i != -1 ; i = edge[i].next){
            int tt = edge[i].to;
            if(dis[tt] > edge[i].quan && !book[tt]){
                dis[tt] = edge[i].quan;
                siftup(pos[tt]);
            }
        }
    }
}
int main()
{
    int u, v, w;
    memset(head, -1, sizeof(head));
    scanf("%d %d", &n, &m);
    for(int i = 1 ; i <= n ; ++ i) dis[i] = INF;
    for(int i = 1 ; i <= m ; ++ i){
        scanf("%d %d %d", &u, &v, &w);
        add(u, v, w); add(v, u, w);
    }
    for(int i = head[1] ; i != -1 ; i = edge[i].next){
        int tt = edge[i].to;
        dis[tt] = min(dis[tt], edge[i].quan);
    }
    init();
    heap();
    prime();
    cout<<ans;
    return 0;
}

你可能感兴趣的:(洛谷,最小生成树)