欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > 2.12日学习总结

2.12日学习总结

2025/2/14 4:08:30 来源:https://blog.csdn.net/2401_88124450/article/details/145598584  浏览:    关键词:2.12日学习总结

题目一:

AC代码: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 设定一个最大的同学数量,这里最多能处理 100010 个同学
#define mx 100010// 造一个小房子,用来装每个同学的信息
// 这个小房子有三个房间
typedef struct {// 第一个房间存这个同学左边同学的编号int l;// 第二个房间存这个同学右边同学的编号int r;// 第三个房间就像一个小牌子,0 表示这个同学要喊出来,1 表示这个同学不喊int d;
} T;// 造一排这样的小房子,能装下最多 mx 个同学的信息
T t[mx];// 这个函数是用来把新同学安排到队伍里的
// i 是队伍里已经有的一个同学的编号
// k 是新同学的编号
// f 就像一个小指令,1 表示新同学要站在 i 同学的左边,其他数表示站在右边
void add(int i, int k, int f) {if (f == 1) {// 如果要站在左边// 先让新同学看看 i 同学右边是谁,然后新同学记住这个人,把他当成自己右边的人t[k].r = t[i].r;// 新同学再把 i 同学当成自己左边的人t[k].l = i;// 接着让 i 同学把新同学当成自己右边的人t[i].r = k;// 最后让原来 i 同学右边的人把新同学当成自己左边的人t[t[k].r].l = k;} else {// 如果要站在右边// 新同学先把 i 同学当成自己右边的人t[k].r = i;// 再看看 i 同学左边是谁,把这个人当成自己左边的人t[k].l = t[i].l;// 让 i 同学把新同学当成自己左边的人t[i].l = k;// 最后让原来 i 同学左边的人把新同学当成自己右边的人t[t[k].l].r = k;}
}int main() {// n 是一共有多少个同学// m 是有几个同学不能喊出来int n, m;// x 用来存输入里已有的同学编号// k 暂时没用,因为新同学编号是按顺序来的// f 是用来决定新同学站哪里的指令int x, k, f;// 从键盘上输入一共有多少个同学,把这个数存到 n 里scanf("%d", &n);// 有一个特殊的“虚拟同学”,编号是 0// 让这个“虚拟同学”的左边和右边都是自己t[0].r = 0;t[0].l = 0;// 把第一个同学,编号 1,安排到“虚拟同学”的左边add(0, 1, 1);// 从第二个同学开始,一个一个地把剩下的同学安排到队伍里for (int i = 2; i <= n; i++) {// 从键盘上输入一个已有的同学编号 x 和一个指令 fscanf("%d %d", &x, &f);// 把编号为 i 的新同学按照指令安排到队伍里add(x, i, f);}// 从键盘上输入有几个同学不能喊出来,把这个数存到 m 里scanf("%d", &m);// 一次一次地处理那些不能喊出来的同学while (m--) {// 从键盘上输入不能喊出来的同学的编号 xscanf("%d", &x);// 把这个同学对应的小牌子(d)翻成 1,表示他不能喊t[x].d = 1;}// 从“虚拟同学”右边的第一个同学开始,一个一个往后看for (int i = t[0].r; i; i = t[i].r) {// 如果这个同学的小牌子是 0,说明他能喊if (t[i].d == 0) {// 把这个同学的编号喊出来,后面再跟个空格printf("%d ", i);}}return 0;
}

题解: 

1.准备工作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define mx 100010typedef struct {int l;int r;int d;
} T;T t[mx];

 常量 mx:我们先设定了一个最大的同学数量 mx,就好像我们提前准备了一个足够大的操场,最多能容纳 100010 个同学。

结构体 T:创建了一个结构体 T,它就像是每个同学的 “信息小卡片”。这个小卡片上有三个信息:l 记录这个同学左边同学的编号,r 记录右边同学的编号,d 就像一个小牌子,0 表示这个同学要参与展示(喊出编号),1 表示不参与。

结构体数组 t:有了小卡片,我们就造了一排这样的小卡片,最多能放 mx 张,用来记录每个同学的信息。

2.新同学加入队伍的函数
void add(int i, int k, int f) {if (f == 1) {t[k].r = t[i].r;t[k].l = i;t[i].r = k;t[t[k].r].l = k;} else {t[k].r = i;t[k].l = t[i].l;t[i].l = k;t[t[k].l].r = k;}
}

这个 add 函数就是负责把新同学安排到队伍里的。

参数i 是队伍里已经有的一个同学的编号,k 是新同学的编号,f 是一个指令。当 f 等于 1 时,新同学要站在 i 同学的左边;当 f 不等于 1 时,新同学站在 i 同学的右边。

操作过程:以新同学站在 i 同学左边为例,首先新同学要看看 i 同学右边是谁,然后把这个人当成自己右边的人;接着新同学把 i 同学当成自己左边的人;再让 i 同学把新同学当成自己右边的人;最后让原来 i 同学右边的人把新同学当成自己左边的人。这样新同学就成功站到 i 同学左边了。站在右边的操作类似。

3. 主函数开始安排同学
int main() {int n, m;int x, k, f;scanf("%d", &n);t[0].r = 0;t[0].l = 0;add(0, 1, 1);

 读取同学总数:通过 scanf 从键盘输入一共有多少个同学,把这个数存到 n 里。

虚拟同学:有一个特殊的 “虚拟同学”,编号是 0。我们先让这个 “虚拟同学” 的左边和右边都指向自己,就像这个 “虚拟同学” 自己站成了一个圈。

第一个同学加入:调用 add 函数,把第一个同学(编号 1)安排到 “虚拟同学” 的左边。

4. 后续同学依次加入队伍
    for (int i = 2; i <= n; i++) {scanf("%d %d", &x, &f);add(x, i, f);}

 从第二个同学开始,一个一个地把剩下的同学安排到队伍里。每次从键盘输入一个已有的同学编号 x 和一个指令 f,然后调用 add 函数把编号为 i 的新同学按照指令安排到队伍里。

5. 让部分同学 “沉默”
    scanf("%d", &m);while (m--) {scanf("%d", &x);t[x].d = 1;}

 读取 “沉默” 同学数量:从键盘输入有几个同学不能参与展示(要 “沉默”),把这个数存到 m 里。

标记 “沉默” 同学:通过循环,每次从键盘输入一个不能参与展示的同学的编号 x,然后把这个同学对应的小牌子(d)翻成 1,表示他不能参与展示。

6. 喊出能展示的同学编号
    for (int i = t[0].r; i; i = t[i].r) {if (t[i].d == 0) {printf("%d ", i);}}return 0;
}

 从 “虚拟同学” 右边的第一个同学开始,一个一个往后看。如果这个同学的小牌子(d)是 0,说明他能参与展示,就把这个同学的编号通过 printf 喊出来,后面再跟个空格。最后程序结束。

总结:整个程序就是通过模拟同学排队、新同学加入和部分同学 “沉默” 的过程,最后输出能参与展示的同学编号,利用结构体数组和链表的思想实现了队伍的管理。

题目二:

AC代码 :

#include <stdio.h>#define N 1000001int f[N], n, m, x, y;// 并查集(路径压缩) 
int r(int x) {if (x != f[x]) {f[x] = r(f[x]); }return f[x]; 
}// 合并 
void u(int x, int y) {int r1 = r(x); int r2 = r(y); f[r1] = r2; 
}int main() {while (1) {int a = 0;scanf("%d", &n);if (n == 0) {break;}scanf("%d", &m);for (int i = 1; i <= n; i++) {f[i] = i;}for (int i = 1; i <= m; i++) {scanf("%d %d", &x, &y);u(x, y);}for (int i = 1; i <= n; i++) {if (r(i) == i) {a++;}}printf("%d\n", a - 1);}return 0;
}

题解:

 1.#define N 1000001:定义一个宏 N,表示数组的最大长度,用于存储并查集的数据。
 2.全局变量定义
int f[N], n, m, x, y;

f[N]:并查集的核心数组,f[i] 表示节点 i 的父节点。初始时,每个节点的父节点是它自己。

n:表示图中节点的数量。

m:表示图中边的数量。

xy:用于临时存储输入的每条边的两个端点。

3. 查找函数 r
// 并查集(路径压缩) 
int r(int x) {if (x != f[x]) {f[x] = r(f[x]); }return f[x]; 
}

 这个函数的作用是查找节点 x 所在集合的根节点(也就是祖先节点)。

if (x != f[x]):如果 x 不是根节点(即 x 的父节点不是它自己),就递归地查找 x 的父节点的根节点,并将 x 的父节点直接设为根节点,这就是路径压缩的过程,目的是为了让后续查找操作更快。

最后返回根节点。

4. 合并函数 u
// 合并 
void u(int x, int y) {int r1 = r(x); int r2 = r(y); f[r1] = r2; 
}

 这个函数的作用是将节点 x 和节点 y 所在的集合合并成一个集合。

首先分别调用 r(x)r(y) 找到 xy 所在集合的根节点 r1r2

然后将 r1 的父节点设为 r2,这样两个集合就合并成一个集合了。

5. 主函数 main
int main() {while (1) {int a = 0;scanf("%d", &n);if (n == 0) {break;}scanf("%d", &m);for (int i = 1; i <= n; i++) {f[i] = i;}for (int i = 1; i <= m; i++) {scanf("%d %d", &x, &y);u(x, y);}for (int i = 1; i <= n; i++) {if (r(i) == i) {a++;}}printf("%d\n", a - 1);}return 0;
}

 while (1):这是一个无限循环,不断读取输入数据,直到输入的节点数量 n 为 0 时退出循环。

int a = 0:用于统计图中连通分量的数量。

scanf("%d", &n)scanf("%d", &m):分别读取节点数量和边的数量。

for (int i = 1; i <= n; i++) { f[i] = i; }:初始化并查集,将每个节点的父节点设为它自己,表示每个节点最初都属于一个单独的集合。

for (int i = 1; i <= m; i++) { scanf("%d %d", &x, &y); u(x, y); }:循环读取每条边的两个端点 xy,并调用 u(x, y) 函数将它们所在的集合合并。

for (int i = 1; i <= n; i++) { if (r(i) == i) { a++; } }:遍历所有节点,统计根节点的数量,也就是连通分量的数量。因为根节点的特点是它的父节点就是它自己,所以当 r(i) == i 时,说明节点 i 是一个根节点。

printf("%d\n", a - 1);:输出将这些连通分量连接成一个连通图所需添加的最少边数。根据图论知识,要将 a 个连通分量连接成一个连通图,最少需要添加 a - 1 条边。

复杂度分析

时间复杂度:并查集的查找和合并操作在使用路径压缩的情况下,平均时间复杂度接近 \(O(1)\)。因此,整个程序的时间复杂度主要由输入的节点数量 n 和边的数量 m 决定,为 \(O(n + m)\)。
空间复杂度:主要使用了一个长度为 n 的数组 f 来存储并查集的信息,所以空间复杂度为 \(O(n)\)。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com