BUAA OJ 382 中世界的Thor【次小生成树】

本文探讨了在给定的图中寻找次小生成树的问题,即在最小生成树基础上,通过巧妙调整使得生成树成本稍高但仍是次优解。文章提供了两种解题思路:一是通过移除最小生成树中的一条边并重新构建生成树;二是寻找最小生成树上两点间最短边,加边成环后再删除最短边形成次小生成树。代码示例使用了C++实现。

题目描述

时间限制: 1000 ms 内存限制: 65536 kb
好久没见Thor了哈,他去了哪里呢?
他去了中世界,在给那里的城镇修铁路。
中世界铁路公司要求Thor建设这样一条铁路: 在连同所有城镇的前提下,使得铁路的总长最短。
这对于Thor自然不是难事咯——不就是求一个最小生成树嘛~
然而Thor是一个贪心的工程师,他想在这其中做一些手脚,使得既满足铁路的布局是一棵生成树,其费用又比最小生成树要大上那么一丢丢,这样他便可以从预算与实际的差价之中收取一定的私利。
然而Thor这样做还是有风险的,一旦被发现后果很严重。于是谨慎起见,他准备找到这样一个方案,在既使得布局是一颗生成树,又使得其费用是除最小生成树以外最小的费用。现在这个事情被交给了你。

输入

多组数据。
对于每一组数据,第一行有两个正整数n和m,表示有n个城镇以及m条可以铺设的铁路路线。(1≤n≤300)
接下来m行,每行有三个正整数x、y、w,表示城镇x到城镇y之间可以铺设一条长度为w的铁路。城镇编号从1开始。 (1≤c≤100)
数据保证没有重边和自环,同时保证图中有环存在。

输出

对于每一组数据,输出得到的生成树的边长之和。

输入样例

3 3
1 2 1
2 3 2
1 3 3

输出样例

4

思路1

先求出最小生成树,然后去掉最小生成树上的一条边,再求这个去掉了一条边的图最小生成树,这些最小生成树中的权值之和最小者(而且顶点必须仍然是n个的)就是原图的次小生成树。O(N3)O(N^3)O(N3)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>

using namespace std;
typedef struct edge {
    int u, v, w;

    edge(int x, int y, int ww) : u(x), v(y), w(ww) {}
} edge;

bool cmp(edge a, edge b) {
    return a.w < b.w;
}

int a[305][305], uf[305];
bool use[305][305];

int find(int num) {
    if (uf[num] == num)return num;
    return uf[num] = find(uf[num]);
}

void connect(int x, int y) {
    int fx = find(x);
    int fy = find(y);
    if (fx != fy) uf[fx] = fy;
}

int kruskal(int n) {
    int edgeNum = 1;
    memset(use, 0, sizeof(use));
    vector<edge> vec;
    vec.reserve(100000);
    for (int x = 1; x <= n; x++)
        for (int y = 1; y <= n; y++)
            if (a[x][y] && x < y)
                vec.emplace_back(x, y, a[x][y]);
    sort(vec.begin(), vec.end(), cmp);
    int sum = 0, cnt = 0;
    for (int i = 1; i <= 300; i++)
        uf[i] = i;
    for (int i = 0; i < vec.size() && cnt < n - 1; i++)
        if (find(vec[i].v) != find(vec[i].u)) {
            sum += vec[i].w;
            connect(vec[i].u, vec[i].v);
            ++cnt;
            use[vec[i].u][vec[i].v] = use[vec[i].v][vec[i].u] = true;
            ++edgeNum;
        }
    return edgeNum == n ? sum : -1;
}

int main() {
    int n, m, x, y, w;
    while (~scanf("%d%d", &n, &m)) {
        memset(a, 0, sizeof(a));
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d", &x, &y, &w);
            a[x][y] = w;
            a[y][x] = w;
        }
        kruskal(n);
        vector<edge> mst;
        mst.reserve(305);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (use[i][j] && i < j)
                    mst.emplace_back(i, j, a[i][j]);
        int t, tst, max = 99999999;
        for (auto &i : mst) {
            t = i.w;
            a[i.u][i.v] = a[i.v][i.u] = 0;
            tst = kruskal(n);
            if (tst < max && tst != -1)
                max = tst;
            a[i.u][i.v] = a[i.v][i.u] = t;
        }
        printf("%d\n", max);
    }
}

思路2

求出最小生成树上任意两点的最短边,任意加入一条边之后必然成环,这时去掉这两点之间的最短边就会得到一棵树,这些树中权值最小的就是次小生成树。O(N2)O(N^2)O(N2)

代码

#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;
typedef long long ll;

struct edge {
    int head;
    int tail;
    int weight;
    bool flag;

    bool operator<(const edge &p) const {
        return weight < p.weight;
    }
};

struct mstNode {
    int rank;
    int weight;
    int next;
};

struct node {
    int rank;
    int max;  // 从某个点到它的路径中的最大边的长度
    explicit node(int r = -1, int m = -1) : rank(r), max(m) {}
};

const int MAX = 100000;

edge edges[MAX];
mstNode msTree[602];
int n, m, num, p[MAX], maxWeight[301][301], Index[301];

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

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

// 是从下向上建树的
void addEdge(int head, int tail, int weight) {
    msTree[num].rank = tail;
    msTree[num].weight = weight;
    msTree[num].next = Index[head];
    Index[head] = num++;
}

inline ll kruscal() {
    int i, x, y, sum = 0;
    makeSet();
    sort(edges, edges + m);
    for (i = 0; i < m; i++) {
        x = findSet(edges[i].head);
        y = findSet(edges[i].tail);
        if (x != y) {
            p[x] = y;
            addEdge(edges[i].head, edges[i].tail, edges[i].weight);
            addEdge(edges[i].tail, edges[i].head, edges[i].weight);
            edges[i].flag = true;
            sum += edges[i].weight;
        }
    }
    return sum;
}

void bfs(int start) {
    int i;
    bool visited[301];
    memset(visited, 0, sizeof(visited));
    queue<node> que;
    node now(start, 0);
    node adj;

    que.push(now);
    visited[start] = true;
    while (!que.empty()) {
        node frontNode = que.front();
        que.pop();
        for (i = Index[frontNode.rank]; i != -1; i = msTree[i].next) {
            adj.rank = msTree[i].rank;
            adj.max = msTree[i].weight;
            if (!visited[adj.rank]) {
                if (frontNode.max > adj.max)
                    adj.max = frontNode.max;
                maxWeight[start][adj.rank] = adj.max;
                visited[adj.rank] = true;
                que.push(adj);
            }
        }
    }
}

ll secondaryMST() {
    int i;
    ll sum = kruscal();
    ll secSum = 999999999;
    ll tem;
    for (i = 1; i <= n; i++)
        bfs(i);
    for (i = 0; i < m; i++)
        if (!edges[i].flag) {
            tem = sum + edges[i].weight - maxWeight[edges[i].head][edges[i].tail];
            if (secSum > tem)
                secSum = tem;
        }
    return secSum;
}

int main() {
    while (~scanf("%d %d", &n, &m)) {
        for (int i = 0; i < m; i++) {
            scanf("%d %d %d", &edges[i].head, &edges[i].tail, &edges[i].weight);
            edges[i].flag = false;
        }
        num = 0;
        memset(Index, -1, sizeof(Index));
        printf("%lld\n", secondaryMST());
    }
}
【源码免费下载链接】:https://renmaiwang.cn/s/nhrcw 深度优先搜索(DFS,Depth-First Search)是一种用于遍历或搜索树或图的算法,它选择一个节点并尽可能深地探索其分支。在迷宫生成中,DFS被用来创建复杂的路径结构。以下是对给定内容的详细解释:1. **Python深度优先算法生成迷宫的原理**: 迷宫生成的基本思想是随机地在空白区域添加墙壁,形成一条可以从起点到终点的路径。DFS在这里的作用是从起始点开始,随机选择一个方向进行移动,并将该路径标记为已访问。当遇到障碍(已存在的墙壁)或者到达终点时,算法回溯到上一步,选择其他未尝试过的路径。2. **代码解析**: - 定义矩阵`dfs`来记录迷宫中每个单元格是否已被访问。 - 定义矩阵`maze`来表示最终生成的迷宫,其中`#`代表墙壁,空格代表可通行路径。 - `operation`字典存储了四个可能的方向(上、下、左、右)对应的坐标偏移量。 - `direction`列表包含所有可能的方向,用于随机选择移动方向。 - `stack`用于存储深度优先搜索过程中的路径。3. **函数说明**: - `show(graph)`:打印迷宫矩阵,便于观察迷宫结构。 - `showRouter(stack)`:打印DFS过程中访问的路径,展示从起点到终点的路径。 - `generateMaze(start)`:核心函数,使用DFS生成迷宫。首先将起始点标记为已访问,然后随机排序方向,依次尝试这些方向,如果新位置未被访问且在有效范围内,则打通墙壁并递归调用自身。4. **迷宫生成流程**: - 创建一个全墙的初始迷宫矩阵,奇数行和奇数列的位置代表实际的墙壁,偶数位置代表路径。 - 起点设为`(0, 0)`,调用`generateMaze((0,0))`开始生成迷宫。 - 在递归过程中,每次
内容概要:本文设计并实现了一个基于ZigBee无线传感器网络的火焰采集系统,旨在通过无线传感技术实现对监测区域火灾的实时检测与远程报警。系统由ZigBee火焰传感器节点、网关模块和远程通信终端(手机)组成,利用CC2530芯片和Z-Stack协议栈完成数据采集、无线传输与网络组网。传感器节点采集火焰信号后,通过ZigBee网络发送至网关,网关再通过GPRS将信息传输至手机端,实现远程监控。文中详细阐述了系统架构、需求分析、硬件电气原理图设计(包括感知节点与网关)、ZigBee协议栈开发、串口通信机制及节点入网流程,完成了驱动程序与通信协议的设计与调试。系统具备低功耗、高可靠性、自组织组网等特点,适用于古建筑、旧楼宇等场景的火灾预警。; 适合人群:具备嵌入式系统、无线通信基础知识的高校物联网、电子信息类专业学生或初级工程技术人员,熟悉C语言及基本电路设计者更佳;适合参与课程设计、毕业设计或从事无线传感网络开发的技术人员参考。; 使用场景及目标:①用于室内或复杂环境中火灾的早期监测与自动报警;②作为物联网与无线传感器网络的教学实践项目,掌握ZigBee协议栈、传感器驱动、串口通信、网络拓扑构建等核心技术;③为智慧消防、智能家居等应用场景提供低成本、易部署的技术方案原型。; 阅读建议:建议结合ZigBee实验箱与IAR开发环境同步实践,重点关注协议栈事件处理机制、串口与无线数据转发逻辑、节点入网流程的代码实现,配合电气原理图理解硬件连接关系,调试过程中注意信道选择、PAN ID设置与数据帧格式校验,以提升系统稳定性与通信可靠性。
【源码免费下载链接】:https://renmaiwang.cn/s/rbkv9 在计算机科学和技术领域中,计算机网络是不可或缺的一环,其实践性教学环节是掌握该学科核心知识的重要途径。华中科技大学的《计算机网络实验详解》课程为学生提供了全面而系统的动手实践机会,涵盖了socket连接建立、可靠数据传输机制设计和CPT(Cisco Packet Tracer)网络模拟与构建等关键实验项目。这些实践内容不仅强化了学生的编程能力,还帮助其深入理解了网络通信的基本原理及其在实际应用中的具体实现方式。通过 socket连接建立实验,学生能够掌握进程间通信的核心技术,并利用socket接口完成客户端与服务器端之间的信息交互。具体而言,该过程包括socket对象创建、绑定IP地址和端口号、监听连接请求、接受连接建立以及数据传输等步骤。这些实践环节让学生深入理解了TCP/IP协议族的工作机制及其在现代网络体系中的重要地位。此外,通过可靠数据传输实验,学生得以探索如何在不稳定的网络环境下确保数据完整性这一技术难点。互联网的特殊性质导致数据可能经历丢失、重复或顺序错误等问题,因此设计和实现一套完善的传输可靠性机制成为课程的重点内容。该部分实践涉及滑动窗口协议、停等协议以及自动重传请求(ARQ)等关键技术,这些方法均建立在经典的TCP协议之上,并通过实验验证了其在提高数据传输可靠性的有效性。最后,CPT组网实验为学生提供了一个模拟真实网络环境的平台,在此环境中他们可以搭建路由器和交换机设备、配置IP地址并设计路由策略,从而实现网络的互联互通。通过这一系列实践环节,学生不仅能够巩固理论知识,还能够在实际操作中掌握网络设备的配置与管理技能。综合来看,《计算机网络实验详解》课程通过 socket连接建立、可靠数据传输机制设计和CPT组网等三大模块的学习,为学生构建了扎实的专业基础,并为其未来从事网络相
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值