主页 > 创业  > 

2.16日学习总结

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 的前缀最小值和后缀最小值,最后通过双指针的方式遍历这些三元组,逐步累加计算结果,最后进行一些调整。

标签:

2.16日学习总结由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“2.16日学习总结