第十一次CCF-CSP认证
- 打酱油
- 满分题解
- 公共钥匙盒
- 满分题解
- solution 1
- solution 2(优先队列优化)
- 通信网络(图的遍历问题)
- 满分题解
打酱油
题目链接
满分题解
思路:做完这题我觉得这里有点像贪心算法但又是常识性问题,
对于我拿到的钱金额为N,首先我确定全部用来买酱油,
我肯定想要用同样的钱买到尽可能多的酱油,所以这里我先尝试一下满5赠2,然后不满足满5赠2的条件之后,
剩余的钱假设为Q,我优先尝试能不能参加满3赠1,最后才是考虑用10块钱换一瓶酱油,
最后加起来就可以,我看了Y总的思路代码很短,但是我感觉我考试的时候还是喜欢用这种无脑的办法写哈哈哈,不想思考一点O(∩_∩)O哈哈~
#include <bits/stdc++.h>
using namespace std;
//基本思路:对于我们输入的N是小于300的一个10的倍数,我们可以先用N去余50,
//得到的余数再去余上30,如果还有那就直接处以10再加起来
int main()
{int n,p,q,a,b;cin>>n;if(n>=50){p=n%50;q=(n-p)/50;//得到能满足几次满五瓶送一瓶}else if(n<50){q=0;}if((n-50*q)>=30){a=(n-50*q)%30;b=(n-q*50-a)/30;}else if(n<30){b=0;}int sum=50*q+30*b;int res=(n-sum)/10+q*7+b*4;cout<<res;
}
公共钥匙盒
以往来说,第二题也就是一般的模拟,但是这题有些恶心了,我看了一下这次的平均分只有130左右,明显偏低的,所以这题能拿满已经比较靠前了,我觉得这题比较恶心的就是这取钥匙和还钥匙这个操作用代码怎么体现,怎么存储起来,特别是怎么样每次才能找到最左边的空挂钩呢,我觉得这是有些不太直白的地方,后来我想了一下,可以用一个结构体来存储每次的操作,至于题目中所体现的顺序问题,因为这里是结构体,需要我们自己重载一下小于号,也就是自己写一个cmp函数来制定规则
题目链接
满分题解
solution 1
#include<iostream>
#include<algorithm>
#include<cstring>using namespace std;// 定义常量 N 表示钥匙的最大数量
const int N = 1010;
// 数组 key 用于模拟钥匙盒,key[i] 表示第 i 个位置上的钥匙编号
int key[N];// 定义结构体 Node 用于存储钥匙的使用信息
struct Node
{int id; // 钥匙的编号int ti; // 钥匙的使用时间(借用时间或归还时间)// 重载小于运算符,用于结构体的排序// 首先比较时间 ti,如果时间相同则比较钥匙编号 idbool operator<(const Node &t)const {if(ti == t.ti) return id < t.id;return ti < t.ti;}
};// 定义两个 Node 类型的数组,q1 存储钥匙的借用信息,q2 存储钥匙的归还信息
Node q1[N], q2[N];// n 表示钥匙的总数,m 表示借还钥匙的记录数
int n, m;int main()
{// 从标准输入读取钥匙的总数 n 和借还钥匙的记录数 mcin >> n >> m;// 初始化钥匙盒,将每个位置上的钥匙编号初始化为其位置编号for(int i = 1; i <= n; i ++) key[i] = i;// 循环读取 m 条借还钥匙的记录for(int i = 0; i < m; i ++){int a, b, c;// 读取钥匙编号 a、借用时间 b 和借用时长 ccin >> a >> b >> c;// 将借用信息存入 q1 数组q1[i] = {a, b};// 计算归还时间并将归还信息存入 q2 数组q2[i] = {a, b + c};}// 对借用信息数组 q1 按照时间和钥匙编号进行排序sort(q1, q1 + m);// 对归还信息数组 q2 按照时间和钥匙编号进行排序sort(q2, q2 + m);// 定义两个指针 i 和 j,分别用于遍历借用信息数组 q1 和归还信息数组 q2int i = 0, j = 0;// 当两个数组都未遍历完时,进行借还操作while (i < m && j < m){// 如果当前借用时间小于当前归还时间if(q1[i].ti < q2[j].ti){// 获取当前要借用的钥匙编号int id = q1[i].id;// 在钥匙盒中查找该钥匙并将其位置置为 0 表示已借出for(int k = 1; k <= n; k ++){if(key[k] == id)key[k] = 0;}// 移动借用信息数组的指针i ++;}// 如果当前借用时间大于等于当前归还时间else if(q1[i].ti >= q2[j].ti){// 获取当前要归还的钥匙编号int id2 = q2[j].id;// 在钥匙盒中查找第一个空闲位置并将钥匙归还到该位置for(int k = 1; k <= n; k ++){if(key[k] == 0){key[k] = id2;break;}}// 移动归还信息数组的指针j ++;}}// 处理可能剩余的未归还钥匙while(j < m){// 获取当前要归还的钥匙编号int id2 = q2[j].id;// 在钥匙盒中查找第一个空闲位置并将钥匙归还到该位置for(int k = 1; k <= n; k ++){if(key[k] == 0){key[k] = id2;break;}}// 移动归还信息数组的指针j ++;}// 输出最终钥匙盒中每个位置上的钥匙编号for(int k = 1; k <= n; k ++)cout << key[k] << ' ';cout << endl;return 0;
}
solution 2(优先队列优化)
其实我在读题目的时候就有点想用优先队列,主要是因为老师每次还钥匙的时候是按照一定规则的,上面的代码固然没问题,但是主要的瓶颈在于每次归还钥匙时,需要遍历钥匙盒来找到第一个空闲位置。
优化思路:
使用优先队列存储空闲位置:使用一个优先队列 available 来存储钥匙盒中的空闲位置,优先队列会自动按照位置编号从小到大排序。
优化借还操作:在借用钥匙时,将对应的钥匙位置从 key 数组中置为 0,并将该位置加入到优先队列 available 中;在归还钥匙时,从优先队列中取出最小的空闲位置,将钥匙归还到该位置。
示例说明
假设钥匙盒有 10 个位置,初始时所有位置都是空闲的,我们可以将位置编号 1 - 10 依次插入到优先队列中。当有钥匙被借用时,将对应的位置从优先队列中移除;当有钥匙归还时,直接从优先队列中取出队首元素(即编号最小的空闲位置),将钥匙归还到该位置。这样可以避免每次都遍历整个钥匙盒来查找空闲位置,从而提高程序的效率。
#include <iostream>
#include <algorithm>
#include <queue>using namespace std;const int N = 1010;
int key[N];struct Node {int id, ti;bool operator<(const Node& t) const {if (ti == t.ti) return id < t.id;return ti < t.ti;}
};Node q1[N], q2[N];
int n, m;int main() {cin >> n >> m;// 初始化钥匙盒for (int i = 1; i <= n; i++) key[i] = i;// 读取借还记录for (int i = 0; i < m; i++) {int a, b, c;cin >> a >> b >> c;q1[i] = {a, b};q2[i] = {a, b + c};}// 对借用信息和归还信息进行排序sort(q1, q1 + m);sort(q2, q2 + m);// 优先队列,用于存储空闲位置priority_queue<int, vector<int>, greater<int>> available;// 初始时所有位置都是空闲的for (int i = 1; i <= n; i++) available.push(i);int i = 0, j = 0;while (i < m && j < m) {if (q1[i].ti < q2[j].ti) {// 借用钥匙int id = q1[i].id;for (int k = 1; k <= n; k++) {if (key[k] == id) {key[k] = 0;available.push(k); // 将该位置标记为空闲break;}}i++;} else {// 归还钥匙int id2 = q2[j].id;int pos = available.top(); // 取出最小的空闲位置available.pop();key[pos] = id2;j++;}}// 处理剩余的未归还钥匙while (j < m) {int id2 = q2[j].id;int pos = available.top(); // 取出最小的空闲位置available.pop();key[pos] = id2;j++;}// 输出最终钥匙盒中每个位置上的钥匙编号for (int k = 1; k <= n; k++) {cout << key[k] << ' ';}cout << endl;return 0;
}
通信网络(图的遍历问题)
PS:第三题实在是太恶心了 我就跳过了,有能力的可以用C++写写看
这是第四题
题目链接
满分题解
思路参考(yxc):
给定一个有向图,判断图中哪些节点满足从该节点出发,通过正向图或反向图可以到达图中的所有其他节点。
说白了就是看一个点能不能到达所有点同时所有点也都能找到他
具体步骤如下:
1.构建邻接表:通过 add 函数构建正向图和反向图的邻接表。
2.深度优先搜索:对于每个节点,分别从该节点出发进行正向图和反向图的深度优先搜索,标记访问过的节点。
3.检查可达性:使用 check 函数检查是否所有节点都能从当前节点出发,通过正向图或反向图到达。
4.统计满足条件的节点数量:遍历所有节点,统计满足条件的节点数量并输出。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
// 定义常量 N 为最大节点数,M 为最大边数
const int N = 1e3 + 5, M = 1e4 + 5;// h 数组用于存储邻接表的头节点信息,二维数组中第二维 0 表示正向图,1 表示反向图
// e 数组用于存储边的终点信息,同样第二维区分正反向图
// ne 数组用于存储邻接表中每个节点的下一条边的编号,第二维区分正反向图
// cnt 数组用于记录正反向图中当前边的编号
int h[N][2], e[M][2], ne[M][2], cnt[2];
// st 数组用于标记节点是否被访问过,第二维区分正反向图
int st[N][2];// 向邻接表中添加边的函数
// u 是起点,v 是终点,type 表示图的类型(0 为正向图,1 为反向图)
void add(int u, int v, int type)
{// 新增一条边,边的编号为 cnt[type] 加 1e[++cnt[type]][type] = v;// 新边的下一条边是当前起点 u 的头边ne[cnt[type]][type] = h[u][type];// 更新起点 u 的头边为新边h[u][type] = cnt[type];
}// 深度优先搜索函数
// u 是当前搜索的节点,type 表示图的类型(0 为正向图,1 为反向图)
void dfs(int u, int type)
{// 遍历当前节点 u 的所有出边for (int i = h[u][type]; i; i = ne[i][type]){// 获取当前边的终点 vint v = e[i][type];// 如果终点 v 还未被访问过if (!st[v][type]){// 标记终点 v 为已访问st[v][type] = 1;// 从终点 v 继续进行深度优先搜索dfs(v, type);}}
}// 检查是否所有节点都能从某个节点出发,通过正向图或反向图到达
// n 是节点的总数
bool check(int n)
{// 遍历所有节点for (int i = 1; i <= n; ++i)// 如果某个节点在正向图和反向图中都未被访问过if (!st[i][0] && !st[i][1]) return false;// 所有节点都能通过正向图或反向图到达,返回 truereturn true;
}int main()
{int n, m;// 输入节点数 n 和边数 mscanf("%d%d", &n, &m);// 循环读取每条边的信息while (m--){int u, v;// 输入边的起点 u 和终点 vscanf("%d%d", &u, &v);// 向正向图中添加边 u -> vadd(u, v, 0);// 向反向图中添加边 v -> uadd(v, u, 1);}// 记录满足条件的节点数量int ret = 0;// 遍历所有节点for (int i = 1; i <= n; ++i){// 每次以不同节点为起点时,重置访问标记数组memset(st, 0, sizeof st);// 标记当前起点在正向图和反向图中都已访问st[i][0] = st[i][1] = 1;// 从当前起点开始进行正向图的深度优先搜索dfs(i, 0);// 从当前起点开始进行反向图的深度优先搜索dfs(i, 1);// 检查是否所有节点都能从当前起点出发,通过正向图或反向图到达if (check(n)) ret++;}// 输出满足条件的节点数量printf("%d", ret);return 0;
}