洛谷

洛谷题单指南-最小生成树-P3623 [APIO2008] 免费道路

原题链接:https://www.luogu.com.cn/problem/P3623

题意解读:n个节点m条边的无向图,其中边有两种类似,一种是“水泥路”,一种是“鹅卵石路”,要求一种生成树方案,正好将n个节点连通,且“鹅卵石路”条数正好是k条。

解题思路:

1、问题分析

由于限制鹅卵石路的条数,而鹅卵石路可以分为两种:

必选的,如果不选则整个图无法连通
非必选的,可以用其他水泥路来代替
思路就是先确定哪些鹅卵石路必选,然后在其余鹅卵石路中继续选择凑足k个,再去水泥路中选择正好能将整个图连通的边。

2、算法思路

类似kruskal算法

先枚举所有水泥路,尽可能的选择能连通各个节点的边,计算出一共选了多少条边,离n-1条边的差距就是必选的鹅卵石路条数。
再枚举所有鹅卵石路,选择边将上一步未连通的部分连通,保存已选择的边。
清空并查集,重新执行类kruskal算法,先加入必选的鹅卵石路。
再枚举其余鹅卵石路,将能导致连通的边加入,凑足k个。如果最终鹅卵石路不足k个,表示无解。
最后枚举水泥路,将所有能导致连通的边加入。如果最终选择的所有边不如n-1,表示无解。
100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 20005, M = 100005;
struct Edge
{
    int u, v, type; 
} edges[M];
int p[N];
vector<int> ans;
int n, m, k;

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void init()
{
    for(int i = 1; i <= n; i++) p[i] = i;
}

int main()
{
    cin >> n >> m >> k;
    for(int i = 1; i <= m; i++)
    {
        cin >> edges[i].u >> edges[i].v >> edges[i].type;
    }
    //将可带来连通的水泥路连通
    init();
    for(int i = 1; i <= m; i++)
    {
        if(edges[i].type == 1)
        {
            int a = find(edges[i].u);
            int b = find(edges[i].v);
            if(a != b)
            {
                p[a] = b;
            }
        }
    }
    //将可带来连通的鹅卵石路连通,并记录,表示必须要选的鹅卵石路
    for(int i = 1; i <= m; i++)
    {
        if(edges[i].type == 0)
        {
            int a = find(edges[i].u);
            int b = find(edges[i].v);
            if(a != b)
            {
                p[a] = b;
                ans.push_back(i);
            }
        }
    }
    //清空并查集,将必须加入的鹅卵石路连通
    init();
    for(int i = 0; i < ans.size(); i++)
    {
        int a = find(edges[ans[i]].u);
        int b = find(edges[ans[i]].v);
        p[a] = b;
    }
    //继续加入鹅卵石路,凑足k个
    for(int i = 1; i <= m; i++)
    {
        if(edges[i].type == 0)
        {
            int a = find(edges[i].u);
            int b = find(edges[i].v);
            if(a != b)
            {
                p[a] = b;
                ans.push_back(i);
                if(ans.size() >= k) break;
            }
        }

    }
    if(ans.size() < k) 
    {
        cout << "no solution";
        return 0;
    }
    //加入水泥路
    for(int i = 1; i <= m; i++)
    {
        if(edges[i].type == 1)
        {
            int a = find(edges[i].u);
            int b = find(edges[i].v);
            if(a != b)
            {
                p[a] = b;
                ans.push_back(i);
            }
        }
    }
    //如果图不连通
    if(ans.size() != n - 1) cout << "no solution";
    else 
    {
        for(int i = 0; i < ans.size(); i++)
            cout << edges[ans[i]].u  << " " << edges[ans[i]].v << " " << edges[ans[i]].type << endl;
    }
    return 0;
}