当前位置: 首页 > news >正文

2.16日学习总结

题目一:

AC代码 :

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define I INT_MAX
#define M 3005
#define N 2005

int n, m, t, s;  // n 表示点数,m 表示边数,t 用于边的计数,s 存储最小生成树的总权值
int h[N], d[N], v[N];  // h 为链式前向星的头数组,d 存储各点到当前最小生成树的最小距离,v 标记点是否已加入最小生成树

// 定义链式前向星的边结构体
typedef struct {
    int o, n;  // o 表示边的终点,n 指向下一条边
    int w;     // w 表示边的权值
} E;

E e[M << 1];  // 存储边的数组

// 链式前向星的加边操作
void a(int x, int y, int z) {
    e[++t].o = y;
    e[t].w = z;
    e[t].n = h[x];
    h[x] = t;
}

// Prim 算法
void p() {
    int i, u = 1;  // u 表示当前正在处理的点

    // 初始化距离数组
    for (i = 1; i <= n; i++) {
        d[i] = I;
    }
    d[1] = 0;

    // 初始化第一个点连接的边的距离
    for (i = h[1]; i; i = e[i].n) {
        if (e[i].w < d[e[i].o]) {
            d[e[i].o] = e[i].w;
        }
    }

    // 循环 n - 1 次,构建最小生成树
    for (i = 1; i < n; i++) {
        int m = I;  // m 存储当前最小的边权
        v[u] = 1;   // 标记当前点已加入最小生成树

        // 找到距离最小生成树最近且未加入的点
        for (int j = 1; j <= n; j++) {
            if (!v[j] && d[j] < m) {
                u = j;
                m = d[j];
            }
        }

        s += m;  // 将最小边权累加到总权值中

        // 更新与新加入点相连的点到最小生成树的距离
        for (int k = h[u]; k; k = e[k].n) {
            int o = e[k].o;
            if (!v[o] && e[k].w < d[o]) {
                d[o] = e[k].w;
            }
        }
    }
}

int main() {
    scanf("%d %d", &n, &m);

    // 读取边的信息并添加到图中
    while (m--) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        a(x, y, z);
        a(y, x, z);
    }

    p();  // 执行 Prim 算法

    printf("%d\n", s);  // 输出最小生成树的总权值

    return 0;
}

题解: 

1.limits.h:引入 INT_MAX,表示整数的最大值,这里用宏定义 I 来表示无限大,用于后续初始化距离数组。

2. 全局变量定义
int n, m, t, s;
int h[N], d[N], v[N];

typedef struct {
    int o, n;
    int w;
} E;

E e[M << 1];

 n:表示图的顶点数量。

m:表示图的边数量。

t:用于链式前向星中边的计数。

s:存储最小生成树的总权值。

h[N]:链式前向星的头数组,用于存储每个顶点的第一条边的编号。

d[N]:存储每个顶点到当前最小生成树的最小距离。

v[N]:标记数组,用于标记每个顶点是否已经被加入到最小生成树中。

E 结构体:定义链式前向星中的边,o 表示边的终点,n 指向下一条边的编号,w 表示边的权值。

e[M << 1]:存储所有边的数组,M << 1 等价于 M * 2,因为是无向图,每条边需要存储两次。

3. 加边函数 a
void a(int x, int y, int z) {
    e[++t].o = y;
    e[t].w = z;
    e[t].n = h[x];
    h[x] = t;
}

 该函数用于将一条边加入到链式前向星中。

e[++t]:先将边的计数器 t 加 1,然后使用新的编号存储这条边。

e[t].o = y:将边的终点设为 y

e[t].w = z:将边的权值设为 z

e[t].n = h[x]:将当前边的下一条边指向顶点 x 原来的第一条边。

h[x] = t:将顶点 x 的第一条边更新为当前边。

4.Prim 算法函数 p
void p() {
    int i, u = 1;

    // 初始化距离数组
    for (i = 1; i <= n; i++) {
        d[i] = I;
    }
    d[1] = 0;

    // 初始化第一个点连接的边的距离
    for (i = h[1]; i; i = e[i].n) {
        if (e[i].w < d[e[i].o]) {
            d[e[i].o] = e[i].w;
        }
    }

    // 循环 n - 1 次,构建最小生成树
    for (i = 1; i < n; i++) {
        int m = I;
        v[u] = 1;

        // 找到距离最小生成树最近且未加入的点
        for (int j = 1; j <= n; j++) {
            if (!v[j] && d[j] < m) {
                u = j;
                m = d[j];
            }
        }

        s += m;

        // 更新与新加入点相连的点到最小生成树的距离
        for (int k = h[u]; k; k = e[k].n) {
            int o = e[k].o;
            if (!v[o] && e[k].w < d[o]) {
                d[o] = e[k].w;
            }
        }
    }
}

初始化部分

将所有顶点到最小生成树的距离 d[i] 初始化为无限大 I,将第一个顶点的距离 d[1] 设为 0,表示从这个顶点开始构建最小生成树。

遍历第一个顶点的所有邻接边,更新这些邻接顶点到最小生成树的距离。

构建最小生成树部分

进行 n - 1 次循环,因为最小生成树的边数等于顶点数减 1。

在每次循环中,先将当前顶点 u 标记为已加入最小生成树。

遍历所有顶点,找到距离最小生成树最近且未加入的顶点 u,并记录最小距离 m

将最小距离 m 累加到总权值 s 中。

遍历新加入顶点 u 的所有邻接边,更新这些邻接顶点到最小生成树的距离。

复杂度分析

时间复杂度:Prim 算法的时间复杂度为 ,其中  是顶点的数量。因为在每次循环中,需要遍历所有顶点来找到距离最小生成树最近的顶点。

空间复杂度:主要的空间开销是存储边和顶点信息的数组,空间复杂度为 ,其中  是顶点数量, 是边的数量。

总结:

最小生成树问题是图论中的经典问题,对于一个连通加权无向图,需要找出一个包含图中所有顶点的树,并且这棵树的所有边的权值之和最小。在题中,输入会给出图的顶点数 n 和边数 m,以及每条边的两个端点和边的权值,我们需要通过 Prim 算法计算出这个图的最小生成树的边权总和。

使用 Prim 算法来解决这个问题。Prim 算法的核心思想是从图中任意一个顶点开始,逐步扩展生成树,每次选择与当前生成树相连的边中权值最小的边,并将这条边的另一个端点加入到生成树中,直到所有顶点都被加入到生成树为止。

题目二:

AC代码: 

#include <stdio.h>
#include <stdlib.h>

#define M 200010
#define L long long

int n, m;
int a[M], h[M], q[M];

typedef struct {
    int x, y, k, i;
} N;

N b[M], c[M];

// 按照 x 排序的比较函数
int cx(const void *A, const void *B) {
    N *pa = (N *)A;
    N *pb = (N *)B;
    if (pa->x == pb->x) {
        return pa->y - pb->y;
    }
    return pa->x - pb->x;
}

// 按照 y 排序的比较函数
int cy(const void *A, const void *B) {
    N *pa = (N *)A;
    N *pb = (N *)B;
    if (pa->y == pb->y) {
        return pb->k - pa->k;
    }
    return pa->y - pb->y;
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    for (int i = 1; i <= m; i++) {
        scanf("%d %d %d", &b[i].x, &b[i].y, &b[i].k);
        c[i] = b[i];
        b[i].i = c[i].i = i;
    }

    qsort(b + 1, m, sizeof(N), cx);
    qsort(c + 1, m, sizeof(N), cy);

    h[n] = a[n];
    for (int i = n - 1; i >= 1; i--) {
        h[i] = (h[i + 1] < a[i]) ? h[i + 1] : a[i];
    }

    q[1] = a[1];
    for (int i = 2; i <= n; i++) {
        q[i] = (q[i - 1] < a[i]) ? q[i - 1] : a[i];
    }

    int l = 1, r = m, t = 0;
    L s = 0;
    while (r >= 1) {
        int nr = b[r].k - t;
        int nl = c[l].k - t;
        int nt = (nl < nr) ? nl : nr;
        s += (L)nt * h[b[r].x] + (L)nt * q[c[l].y];
        t += nt;
        while (r >= 1 && b[r].k <= t) {
            r--;
        }
        while (l <= m && c[l].k <= t) {
            l++;
        }
    }
    s -= q[c[1].y];
    printf("%lld\n", s);
    return 0;
}

题解: 

1.stdlib.h:提供 qsort 函数,用于对数组进行排序。

2.全局变量定义
int n, m;
int a[M], h[M], q[M];

typedef struct {
    int x, y, k, i;
} N;

N b[M], c[M];

n:表示数组 a 的长度。

m:表示三元组的数量。

a[M]:存储输入的数组元素。

h[M]:存储数组 a 的后缀最小值,即从每个位置开始到数组末尾的最小值。

q[M]:存储数组 a 的前缀最小值,即从数组开头到每个位置的最小值。

N 结构体:用来存储三元组信息,x 和 y 是区间相关的位置,k 是一个计数相关的值,i 是编号。

b[M] 和 c[M]:分别存储三元组,会按照不同规则进行排序。

3.排序比较函数
// 按照 x 排序的比较函数
int cx(const void *A, const void *B) {
    N *pa = (N *)A;
    N *pb = (N *)B;
    if (pa->x == pb->x) {
        return pa->y - pb->y;
    }
    return pa->x - pb->x;
}

// 按照 y 排序的比较函数
int cy(const void *A, const void *B) {
    N *pa = (N *)A;
    N *pb = (N *)B;
    if (pa->y == pb->y) {
        return pb->k - pa->k;
    }
    return pa->y - pb->y;
}

cx 函数:用于对三元组按照 x 进行排序,如果 x 相等,则按照 y 从小到大排序。

cy 函数:用于对三元组按照 y 进行排序,如果 y 相等,则按照 k 从大到小排序。

4.主函数 
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
}
for (int i = 1; i <= m; i++) {
    scanf("%d %d %d", &b[i].x, &b[i].y, &b[i].k);
    c[i] = b[i];
    b[i].i = c[i].i = i;
}

 首先读取 n 和 m 的值。

接着读取数组 a 的 n 个元素。

然后读取 m 个三元组,将其存储在 b 数组中,同时复制一份到 c 数组,并为每个三元组赋予编号。

排序部分
qsort(b + 1, m, sizeof(N), cx);
qsort(c + 1, m, sizeof(N), cy);

 使用 qsort 函数对 b 数组按照 x 排序,对 c 数组按照 y 排序。

计算前缀和后缀最小值
h[n] = a[n];
for (int i = n - 1; i >= 1; i--) {
    h[i] = (h[i + 1] < a[i]) ? h[i + 1] : a[i];
}

q[1] = a[1];
for (int i = 2; i <= n; i++) {
    q[i] = (q[i - 1] < a[i]) ? q[i - 1] : a[i];
}

 计算后缀最小值 h 数组,从数组末尾开始往前遍历,不断更新每个位置的最小值。

计算前缀最小值 q 数组,从数组开头开始往后遍历,不断更新每个位置的最小值。

核心
int l = 1, r = m, t = 0;
L s = 0;
while (r >= 1) {
    int nr = b[r].k - t;
    int nl = c[l].k - t;
    int nt = (nl < nr) ? nl : nr;
    s += (L)nt * h[b[r].x] + (L)nt * q[c[l].y];
    t += nt;
    while (r >= 1 && b[r].k <= t) {
        r--;
    }
    while (l <= m && c[l].k <= t) {
        l++;
    }
}

 使用双指针 l 和 r 分别指向 c 数组和 b 数组的起始和末尾位置。

t 用于记录已经处理的计数总和。

s 用于存储最终结果。

在每次循环中,计算 nr 和 nl 分别表示当前三元组还未处理的计数,取两者较小值 nt

将 nt 乘以对应位置的后缀最小值和前缀最小值并累加到 s 中。

更新 t 的值,同时移动指针 l 和 r,跳过已经处理完的三元组。

结果调整与输出 
s -= q[c[1].y];
printf("%lld\n", s);

 对结果 s 进行调整,减去 q[c[1].y]

输出最终结果 s

总结:

输入包含一个长度为 n 的数组 a,以及 m 个三元组 (x, y, k)。需要根据这些信息,结合数组 a 的前缀最小值和后缀最小值,计算出一个最终的结果 ans。整体思路是先对输入的三元组按照不同规则排序,接着计算数组 a 的前缀最小值和后缀最小值,最后通过双指针的方式遍历这些三元组,逐步累加计算结果,最后进行一些调整。

相关文章:

  • Flutter 记一次疑难杂症
  • 【HUSTOJ 判题机源码解读系列04】判题机常见技术选择方案
  • 响应式布局学习笔记
  • BT401双模音频蓝牙模块如何开启ble的透传,有什么注意事项
  • 从零到一:Spring Boot 与 RocketMQ 的完美集成指南
  • GPT-4o悄然升级:能力与个性双突破,AI竞技场再掀波澜
  • Go 模块管理工具 `go mod tidy` 和 `go.sum` 文件详解
  • 在 Android 上自定义编译 FFmpeg
  • 嵌入式Linux系统SPI驱动移植专题详解(3000+字图文实战指南)
  • 康耐视CAM-CIC-10MR-10-GC工业相机
  • 《TSP6K数据集进行交通场景解析》学习笔记
  • 计算机网络(4)TCP断开
  • MySQL中ddl操作或创建索引防止锁表的一些建议或解决方案
  • 深度优先和广度优先【栈、堆前端举例】
  • 【数据结构初阶第十节】队列(详解+附源码)
  • [LeetCode]day25 151.翻转字符串里的单词
  • Spring Boot中使用Server-Sent Events (SSE) 实现实时数据推送教程
  • 力扣144. 二叉树的前序遍历145. 二叉树的后序遍历94. 二叉树的中序遍历(递归版)
  • 市盈率(P/E Ratio):理解股票价格与盈利的关系(中英双语)
  • 尚硅谷爬虫note008
  • 肖钢:一季度证券业金融科技投资强度在金融各子行业中居首
  • 解放日报:“感觉全世界人都在上海买买买”
  • 外企聊营商|上海仲裁:化解跨国企业纠纷的“上海路径”
  • 病愈出院、跳大神消灾也办酒,新华每日电讯:农村滥办酒席何时休
  • 大外交丨3天拿下数万亿美元投资,特朗普在中东做经济“加法”和政治“减法”
  • 中国首艘海洋级智能科考船“同济”号试航成功,可搭载水下遥控机器人