Roads and Libraries(最小生成树)

针对一个遭受龙卷风破坏的国家,本篇介绍了一种算法来确定修复道路和重建图书馆的最低成本,确保所有市民都能访问到图书馆。

The Ruler of HackerLand believes that every citizen of the country should have access to a library. Unfortunately, HackerLand was hit by a tornado that destroyed all of its libraries and obstructed its roads! As you are the greatest programmer of HackerLand, the ruler wants your help to repair the roads and build some new libraries efficiently.

HackerLand has  cities numbered from  to . The cities are connected by  bidirectional roads. A citizen has access to a library if:

  • Their city contains a library.
  • They can travel by road from their city to a city containing a library.

The following figure is a sample map of HackerLand where the dotted lines denote obstructed roads:

image

The cost of repairing any road is  dollars, and the cost to build a library in any city is  dollars.

You are given  queries, where each query consists of a map of HackerLand and value of  and .

For each query, find the minimum cost of making libraries accessible to all the citizens and print it on a new line.

Input Format

The first line contains a single integer, , denoting the number of queries. The subsequent lines describe each query in the following format:

  • The first line contains four space-separated integers describing the respective values of  (the number of cities),  (the number of roads),  (the cost to build a library), and  (the cost to repair a road).
  • Each line  of the  subsequent lines contains two space-separated integers,  and , describing a bidirectional road connecting cities  and .

Constraints






  • Each road connects two distinct cities.

Output Format

For each query, print an integer denoting the minimum cost of making libraries accessible to all the citizens on a new line.

Sample Input

2
3 3 2 1
1 2
3 1
2 3
6 6 2 5
1 3
3 4
2 4
1 2
2 3
5 6

Sample Output

4
12

Explanation

We perform the following  queries:

  1. HackerLand contains  cities connected by  bidirectional roads. The price of building a library is  and the price for repairing a road is 
    image

    The cheapest way to make libraries accessible to all is to:

    • Build a library in city  at a cost of .
    • Repair the road between cities  and  at a cost of .
    • Repair the road between cities  and  at a cost of .

    This gives us a total cost of . Note that we don't need to repair the road between cities  and  because we repaired the roads connecting them to city !

  2. In this scenario it's optimal to build a library in each city because the cost of building a library () is less than the cost of repairing a road (). image

    There are  cities, so the total cost is .

  3. 题目大意: 一个国家的路和图书馆都被损坏了, 脑残国王想让每个城市都能有或者通过游历别的国家到达图书馆, 给你几个点和几条被损坏的路和修一个图书馆和修一条路的花费,让你判断最小花费。
    这道题卡了我至少一个点!我的大体思路就是一次类似最小生成树操作, 得出最小生成树的边数(包括各个连通分量的)和联通分量数, 然后就有两种方案:要么全建图书馆, 要么在一个连通分量里建一个图书馆并把其余路给修通。 一直感觉思路没问题! 赛后试了各种数据, 终于发现了个BUG! Vector数组没清零!多么痛的领悟! 另外这个还有个坑点就是最后结果得用longlong, 不然会爆。 并查集貌似也能作。

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <vector>
    #include <cstring>
    #include <queue>
    #define N 0x3f3f3f3f
    #define M 100100
    using namespace std;
    long long int n;
    int cr, cl;
    int m;
    vector <int> Map[M];
    int vis[M];//标记i是否访问
    int prim(int s);//prim算法找最小生成树
    int judge(int s);//判断是否还有未访问的点, 否则返回未访问点
    void judge2(int u, int v);//主要是判断Map里有没有重边
    int main()
    {
        int q;
        scanf("%d", &q);
        while(q--)
        {
            memset(vis, 0, sizeof(vis));
            scanf("%lld%d%d%d", &n, &m, &cl, &cr);
            int u, v;
            for(int i = 0; i < m; i++)
            {
                scanf("%d%d", &u, &v);
                judge2(u, v);
                judge2(v, u);
            }
            long long int e_cnt = prim(1);//敲黑板! 要用长整型存最小生成树边数, 不然最后一步计算会爆!
            long long int v_cnt = 1;//同上, 存连通分量数
            int x = 1;//记录遍历点
            while(x)
            {
                x = judge(x);//从x点开始判断是否还有未访问的点, 有的话就连通分量数加一,e_cnt加上从这点开是最小生成树边数
                if(!x)
                {
                    break;
                }
                else
                {
                    e_cnt += prim(x);
                    v_cnt ++;
                }
            }
            long long int cost1 = e_cnt * cr + v_cnt * cl;//会爆int的一步。。。。
            long long int cost2 = n * cl;//同上
            printf("%lld\n", min(cost1, cost2));
            for(int i = 0; i <= n; i++)
            {
                Map[i].clear();//vector 清零!多么痛的领悟!
            }
        }
        return 0;
    }
    int prim(int s)//算是prim算法一个小小的变化吧
    {
        queue <int> q;
        int cnt = 0;
        q.push(s);
        vis[s] = 1;
        while(!q.empty())
        {
            int t = q.front();
            q.pop();
            int S = Map[t].size();
            for(int i = 0; i < S; i++)
            {
                int tt = Map[t][i];
                if(!vis[tt])
                {
                    vis[tt] = 1;
                    q.push(tt);
                    cnt ++;
                }
            }
        }
        return cnt;
    
    }
    int judge(int s)
    {
        for(int i = s; i <= n; i++)
        {
            if(!vis[i])
                return i;
        }
        return 0;
    }
    void judge2(int u, int v)
    {
        int S = Map[u].size();
        for(int i = 0; i < S; i++)
        {
            if(Map[u][i] == v)
                return;
        }
        Map[u].push_back(v);
    }
    

### 关于最小生成树的 Prim 算法在 PTAC 语言中的实现 PTAC 是一种假设的编程语言,因此其语法和特性需要基于对现有编程语言的理解进行模拟。以下是关于 Prim 算法在 PTAC 语言中的实现或解释。 Prim 算法是一种用于求解图的最小生成树的经典算法,其核心思想是从一个顶点开始逐步扩展已选择的顶点集合,直到覆盖所有顶点[^1]。具体来说,算法始终保持一棵树,并通过不断选择连接当前树与未访问顶点之间的最短边来扩展这棵树。 以下是一个基于伪代码的 Prim 算法在 PTAC 语言中的实现示例: ```ptac function primAlgorithm(graph, startNode): // 初始化数据结构 let visited = array of size graph.size initialized to false let key = array of size graph.size initialized to infinity let parent = array of size graph.size initialized to -1 let priorityQueue = new MinHeap() // 设置起始节点的关键值为 0 key[startNode] = 0 priorityQueue.insert(startNode, 0) while not priorityQueue.isEmpty(): u = priorityQueue.extractMin() visited[u] = true // 遍历相邻节点 for each v in graph.neighbors(u): if not visited[v] and graph.weight(u, v) < key[v]: parent[v] = u key[v] = graph.weight(u, v) priorityQueue.update(v, key[v]) return (parent, key) // 示例调用 let N = input("Enter number of towns: ") let M = input("Enter number of roads: ") let graph = new Graph(N) for i from 1 to M: let u = input("Enter town 1 for road " + i + ": ") let v = input("Enter town 2 for road " + i + ": ") let cost = input("Enter budget cost for road " + i + ": ") graph.addEdge(u, v, cost) let startNode = 1 let result = primAlgorithm(graph, startNode) print("Minimum Spanning Tree edges:") for each node in result.parent: if result.parent[node] != -1: print(result.parent[node], "->", node, ":", result.key[node]) ``` #### 解释 1. **初始化**:`key` 数组记录从当前生成树到每个未访问顶点的最小权重,初始值为无穷大。`parent` 数组记录生成树中每个节点的父节点。 2. **优先队列**:使用最小堆(MinHeap)维护未访问顶点及其对应的 `key` 值,以便快速找到具有最小权重的顶点。 3. **扩展树**:每次从优先队列中取出具有最小 `key` 值的顶点,将其标记为已访问,并更新其邻居节点的 `key` 值。 4. **结果输出**:最终返回生成树的边及其权重。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值