二面题解
A.P1258 小车问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(特殊)
小学奥数题/物理题
分析
行程问题不画图,亲人鼻涕两行泪
- 我们假设小车先带乙走,开了
x
米后把乙放下,折返回来找到一直在走的甲,载着他与行走的乙同时到达终点 - 有了上面的图,可以很明显的算出这段走
x
米的时间为2x / (a + b)
,车一直在走,甲也一直在走,可以看成一个相遇问题 - 因为要求同时到达,那么基于最优的路线上面算出来的这段时间一定与把乙放下来走到终点的时间
(s - x) / a
相同 - 有了上面两条的推理,就可以依据时间相等列方程解出
x
的的表达式,再以乙的视角,车速走了x
米,人速走了s - x
米算出总时间了
代码实现
#include <stdio.h>
int main() {
int a, b, s;
scanf("%d %d %d", &s, &a, &b);
double x = (double)(a + b) * s / (3 * a + b);
printf("%6lf", x / b + (s - x) / a);
return 0;
}
B.[P1125 NOIP2008 提高组] 笨小猴 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(易)
哈希表的简单运用
分析
观察题目,映入眼帘的是判断质数的关键词,所以首先需要写出一个函数来实现相应功能
质数:一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数
这里就直接给出实现了
int judge(int n) {
if (n <= 1) return 0;
if (n == 2) return 1;
for (int i = 3; i < sqrt(n); i += 2) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
-
找到关键词出现最多和最少的字符,很显然可以用数组模拟一个简单的哈希表去实现,将数组中相应位数的字符进行统计即可
-
最大次数
maxn
与最小值minn
需要往反方向进行初始化,否则会导致比较时的错误
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int judge(int n) {//判断质数
if (n <= 1) return 0;
if (n == 2) return 1;
for (int i = 3; i < sqrt(n); i += 2) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
int main() {
char s[999];
gets(s);
if (s[0] == '\0') {
return 0;
}
int maxn = 0, minn = 999;//反方向初始化
int map[200];
memset(map, 0, sizeof(map));
for (int i = 0; i < strlen(s); i++) {
map[s[i]]++;
}
for (int i = 0; i < 200; i++) {//模拟哈希表加入值2
if (map[i] != 0) {
maxn = fmax(maxn, map[i]);
minn = fmin(minn, map[i]);
}
}
if (judge(maxn - minn)) {
printf("Lucky Word\n%d", maxn - minn);
}
else {
printf("No Answer\n0");
}
return 0;
}
C.P1506 拯救oibh总部 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(难)
搜索dfs
或bfs
,这里给出dfs
思路(染色问题)
分析
唉这道题真的很悲伤,我们需要知道两个基地点位是可能连着的,不能用简单的for
逻辑去判断
for (int i = 1; i < x - 1; i++) {
for (int j = 1; j < y - 1; j++) {
if (map[i - 1][j] == '*' && map[i][j - 1] == '*' && map[i + 1][j] == '*' && map[i][j + 1] == '*' && map[i][j] == '0') {//左右或者上下都有可能有点位连着,而整体被围住,这种逻辑会判错
t++;
}
}
}
其实可以再加四个循环去找到相应的边界,想一想就收手吧,这种叠着一堆循环的东西,能想不到要用递归解都难
这道题本质上还是在找东西,还要用递归去解,搜索不就闻着味出来了
但因为博主还没有系统的学深过深搜和广搜,这道题就先略过了,以后有了更深的理解后再来补题解
——————————————————————
递归调用一直都是很难的东西,博主也是在完成了代码随想录的二叉树和回溯章节后才对这道题有所领悟
解这道题前至少我们要知道dfs
的模板:
void dfs(参数) {
if (退出条件) return;
for (循环条件) {
dfs(参数);
}
}
dfs
通常用递归实现比较合适,在这个板子里for
循环进行横向遍历,for
里面的递归调用进行纵向遍历
题目要求解不被淹没的基地个数,那我们就用dfs
模拟洪水蔓延的过程,淹没掉外围的基地(在原数组中将基地直接变成障碍即可,当然也可以额外的设置一个标记数组进行记录),而有四面障碍保护的基地经过一轮模拟后就可以进行统计了
对应的这道题里for
选择当前格洪水前进的四个方向,而里面的递归调用则将选择变为现实,至于退出条件限定边界的同时要注意当前位置是否为障碍来决定是否继续蔓延
现在剩下一个问题,我们把洪水放在哪让它开始蔓延,所有读入的位置都有可能有障碍物而导致洪水刚放下就没了,所以放洪水要放在一个读入不到的位置,那我们可以在读入就从(1,1)开始作为有效区域,这样直接让dfs
从(0,0)处开始即可
代码实现
//下次一定QwQ
//这次一定!
#include<stdio.h>
int n, m;
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};//选择方向,这个东西等价于不要循环直接写四个dfs的写法,初见很容易懵,就是四个dfs语句模拟了四个方向
char map[501][501];
void dfs(int x, int y) {
if (x < 0 || x > n + 1 || y < 0 || y > m + 1 || map[x][y] == '*') return;//因为从(0,0)开始了,所以边界要大一圈
map[x][y] = '*';
for (int i = 0; i < 4; i++) {
dfs(x + dir[i][0], y + dir[i][1]);
}
}
int main(){
int res = 0;
scanf("%d %d ", &n, &m);
for(int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%c ", &map[i][j]);//这个读入一定要加一个空格!!!!不然读入都是错的!!!!
}
}
dfs(0, 0);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (map[i][j] == '0') res++;
}
}
printf("%d", res);
return 0;
}
D.P1160 队列安排 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(难)
链式结构的灵活实现
分析
当我们完成阅读理解后,会发现这个“队列”不是指数据结构里那个队列,完全就是一个链表的实现
下面有请基于该思路的四十分代码闪亮登场
#include <stdio.h>
#include <stdlib.h>
typedef struct Deque {
int val;
struct Deque* next;
}*DEQUE;
DEQUE NewMum(int val) {
DEQUE newMum = (DEQUE)malloc(sizeof(struct Deque));
newMum->val = val;
newMum->next = NULL;
return newMum;
}
DEQUE Enque(int flag, DEQUE q, int pos, int newVal) {
DEQUE newMun = NewMum(newVal);
DEQUE move = q;
if (flag == 1) {
while (move != NULL) {
if (move->val == pos) {
break;
}
move = move->next;
}
if (move->next == NULL) {
move->next = newMun;
return q;
}
newMun->next = move->next;
move->next = newMun;
}
else {
DEQUE dummy = (DEQUE)malloc(sizeof(struct Deque));
dummy->next = q;
DEQUE pre = dummy;
while (move != NULL) {
if (move->val == pos) {
break;
}
pre = move;
move = move->next;
}
if (move != NULL) {
pre->next = newMun;
newMun->next = move;
return dummy->next;
}
}
return q;
}
void Deque(int val, DEQUE q) {
DEQUE dummy = (DEQUE)malloc(sizeof(struct Deque));
dummy->next = q;
DEQUE pre = dummy;
DEQUE move = q;
while (move != NULL) {
if (move->val == val) {
break;
}
pre = move;
move = move->next;
}
if (move == NULL) {
return;
}
pre->next = move->next;
}
int main() {
int n;
scanf("%d", &n);
DEQUE q = (DEQUE)malloc(sizeof(struct Deque));
q->val = 1;
q->next = NULL;
int k, p;
for (int i = 2; i <= n; i++) {
scanf("%d %d", &k, &p);
q = Enque(p, q, k, i);
}
int m;
scanf("%d", &m);
int x;
for (int i = 1; i <= m; i++) {
scanf("%d", &x);
Deque(x, q);
}
DEQUE move = q;
while (move != NULL) {
printf("%d ", move->val);
move = move->next;
}
return 0;
}
这毕竟是道算法题,每一个数据的删除都遍历一遍链表未免显得太抽象了,TLE
也是必然的
链表不行,考虑考虑数组,查询的快,可一开始就因为这道题是要在中间插入数据的直接排除了数组的实现
倒也不是说不能用数组了,这道题要求插入的效率,而插入又要先进行查询,只有数组的O(1)
才能满足需求
- 看到这篇文章的相信都是学过链表操作的,而链表中有一种特殊的静态链表,这里贴一篇之前上传的一点拙见,以便对这种思想有一定的了解week4-CSDN博客(写的比较早了其实写的挺差的)
- 静态链表的指针域用一个游标实现,在数组内实现了类似于用真指针那样的链式链接,本题解决的关键就在这里,每个人有两只手,自然要有两个游标进行连接
- 为了方便各个数据的插入,本题的实现其实还有一点最简单的哈希思想,以数组下标为键
key
和值value
插入具体的数据,键和值同时都是数组的下标,访问直接找下标即可 - 有了这两点的明确,我们就可以实现一个在数组内的链式链接,查询有了哈希的
O(1)
,从而解决超时问题
实现的大致思路有了,下面来说说具体的插入和删除
- 我们默认每个人都在那,即数组开多大就有多少人,添加的时候更改其左右游标的连接即可
举个例子,我们现在想把一个人插到目标人物的右边
我们现在有人群结构体数组Pos
,目标Pos[pos]
,需要插入的人Pos[num]
,通过更改他们左右手牵的人即可实现插入
Pos[num].l = pos;//将插入人物的左手递给目标
Pos[num].r = Pos[pos].r;//将插入人物的右手递给目标右边的人
Pos[pos].r = num;//更改目标人物的右牵给插入人物
Pos[Pos[num].r].l = num;//这里与图中不一样,原因是代码中先更改了目标人物的右手指向,引用图中原来的关系会引用到插入人物身上,而上面几行我们已经将插入人物的右手给了目标右边的人,所以需要引用插入人物的右手才能找到原来那个人,并将他的左手给插入人物
而删除因为洛谷的模式要求输出的正确性,可以给每个人添加一个标记位,检查标记位的值决定是否输出即可
- 接下来实现代码就行了?吗?输出怎么输出,按我们上面的逻辑插入后,怎么确定一个人右手边没人了,每个人在插入时都是基于原来两个人插入的,不停去找右边的人时总会返回到原点再来一遍进入死循环
所以在一切的开始前我们要标记一个不存在的0号人物,让最开始在队列的一号人物插到0号人物右边,最后输出的时候总会回来转一圈转到0号人物身上,这个时候停止即可,让他的左右手都牵着自己
for (int i = Pos[0].r; i; i = Pos[i].r) //输出的循环逻辑
代码实现
#include <stdio.h>
#include <stdlib.h>
struct pos{
int l;//左手
int r;//右手
int flag;//标记位
}Pos[100010] = {0};//后面数据比较多,数组(人数)开大一点
void add(int flag, int pos, int num) {
if (flag == 1) {//加到右边
Pos[num].l = pos;
Pos[num].r = Pos[pos].r;
Pos[pos].r = num;
Pos[Pos[num].r].l = num;
}
else {//加到左边
Pos[num].r = pos;
Pos[num].l = Pos[pos].l;
Pos[pos].l = num;
Pos[Pos[num].l].r = num;
}
return;
}
int main() {
Pos[0].l = 0;//设置0号人物作为输出结束的标志
Pos[0].r = 0;
add(1, 0, 1);
int n;
scanf("%d", &n);
int k, p;
for (int i = 2; i <= n; i++) {
scanf("%d %d", &k, &p);
add(p, k, i);//插入人物
}
int m, x;
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%d", &x);
Pos[x].flag = 1;//删除人物
}
for (int i = Pos[0].r; i; i = Pos[i].r) {
if (Pos[i].flag != 1) {//根据标记位是否输出
printf("%d ", i);
}
}
return 0;
}
E.B3640 T3 句子反转 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(中等)
用双指针对字符串进行操作
分析
- 首先题目要求整体上的逆序输出,自然遍历数组时应当从字符串末向头遍历
- 其实数字部分要局部反转,而字母部分则是进行大小写的转化但单词不反转
- 最后题目中还告诉我们每个词之间用空格隔开了
这是从题目中可以直接获取的关键信息,既然已经明确告诉我们用空格隔开了每一个词,第一反应一定是能不能依据这个特性把每一个分开成一个个单独的整体进行处理
当然也不用真的把这些全部一个个拿出来单独处理,这里可以使用双指针进行操作,倒序遍历时左指针找到前一个空格,而右指针在当前词的末尾即可
操控左右指针以及控制输出具体哪一端就可以完成不同的处理,题目保证数字和字母不会相邻,那就直接暴力去找就可以了
代码实现
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char s[101000];//记得把数组开大不然会爆RE
gets(s);
int left = strlen(s) - 1;
int right = strlen(s) - 1;
for (int i = strlen(s) - 1; i >= 0; i--) {
while (left >= 0 && s[left] != ' ') {
left--;
}
int tep = left;
left++;
while (left <= right) {
if (s[right] >= '0' && s[right] <= '9') {
printf("%c", s[right]);
right--;
}
else if (s[right] >= 'a' && s[right] <= 'z') {
printf("%c", s[left] - 32);//转大写
left++;
}
else if (s[right] >= 'A' && s[right] <= 'Z') {
printf("%c", s[left] + 32);//转小写
left++;
}
}
left = tep - 1;
right = tep - 1;
i = tep;
printf(" ");
}
return 0;
}
F.[P1059 NOIP2006 普及组] 明明的随机数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(易)
排序
分析
-
还是那句话,这道题在洛谷上不在力扣上,所以去重可以只在表面上去重,即不输出即可,不用真正的删掉
-
题目要从小到大输出那就先排序,遍历确定重复的数量,输出时遇到相同时跳过即可
代码实现
#include <stdio.h>
#include <stdlib.h>
void BubbleSort(int* s, int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (s[j] > s[j + 1]) {
int tep = s[j];
s[j] = s[j + 1];
s[j + 1] = tep;
}
}
}
}
int main() {
int n;
scanf("%d", &n);
int s[n];
for (int i = 0; i < n; i++) {
scanf("%d", &s[i]);
}
BubbleSort(s, n);
int t = 0;
for (int i = 1; i < n; i++) {
if (s[i] == s[i - 1]) {
t++;
}
}
printf("%d\n", n - t);
for (int i = 0; i < n; i++) {
if (i > 0 && s[i] == s[i - 1]) {//表面去重
continue;
}
printf("%d ", s[i]);
}
return 0;
}
G.P3817 小A的糖果 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
考察知识点(中等)
贪心算法
分析
- 首先不要把题读错了,是一盒盒糖果相邻在一起进行进行处理,每次吃掉糖果不是说直接给当前位的糖果清零了
- 贪就要吝啬一点,从左往右遍历一遍,每次相邻两位之和多于要求的
x
时,将相对右边的糖果数量减少,直到两盒糖果数刚好等于x
即可,一次遍历中靠右的糖果数减少了,下一轮它就成为靠左边的糖果再继续判断(不要忘了把变化的糖果数加入结果中) - 但这样会有一个问题,遍历一定是从第二盒开始的,减少也是从第二盒开始,如果出现第一盒一盒的糖果数就直接多于
x
那就会暴毙,所以在开始一次遍历前需要对首盒糖果单独处理
代码实现
#include <stdio.h>
int main() {
int n, x;
scanf("%d %d", &n, &x);
long long int a[n];
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
}
long long int res = 0;
if (a[0] > x) {//处理第一盒的情况
res = a[0] - x;
a[0] = x;
}
for (int i = 1; i < n; i++) {//一次遍历
if (a[i] + a[i - 1] > x) {
res += a[i] + a[i - 1] - x;
a[i] -= a[i] + a[i - 1] - x;
}
}
printf("%lld", res);
return 0;
}
个人本次总结
本次考核写的很难蚌,1400的总分斩获260,属于是我自己都看不下去的那种
- 写不出来说明我硬实力还是不够,菜就多练始终适用,A题数学问题卡思路,C题的深搜没怎么接触,D题算是比较活了,用了一个类似于静态链表的游标实现操作,没接触过还情有可原,不过考核时写的一个链表去操作也卡死在了虚拟头结点的处理上(后续考核完发现问题补完代码也是超时,更难蚌了),B题就纯属自身问题了,把初始化整错了,甚至判断质数的函数一开始也写错了,整个人感觉完全不在状态
- 长时间练习力扣的核心代码模式,重新写洛谷上的题像是残疾一样,比如C题的二维数组甚至读入错误,没有注意到换行符的影响(虽然注意到这点思路也是错的,但至少不会在这上面折了时间),以及D题里对虚拟头节点的操作,力扣上操作后通常是要将
dummy->next
作为返回值的,结果我是自己写的void
返回值导致半天找不到错误,又消耗了一大段时间 - 时间分配不均以及勇气不够,值300分(最大分值)的G题看了一眼是贪心,就直接断定短时间内绝对写不出来,到最后连题看都没看完,结果难度也就那样,和ABCD纠缠了很久之后脑子直接糊了,E题也想不到是双指针去做,后续做出来了结果因为数组开的不够大一个测试点都没过(别太搞笑)
可秒的BF没秒出来,稍加思索能做的E也因为不熟悉洛谷的RE
报错代表什么而败北,A题特殊暂且不谈,CD纯纯浪费时间了,自身太菜没能写出思路中可以运行的代码导致一直死揪着不放,没能及时止损,把G给扔了
种种迹象都指向唯一的结果,我太菜了,今后要加紧学习了