文章目录
A - 咕咕东的奇遇
咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。
Input
zeus
Output
18
思路
这道题挺简单,26个字母,对上一个字母与当前字母正反取距离,判断那一条路更近,将举例累加。
心得
从这道题开始我用起了一点宏定义技巧,真香。
代码
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define _for(i,s,e) for(int i=s;i<e;i++)
//旋转找最小总距离,其实只要每次旋转前判断顺逆时针那一个更近就好了
int main() {
char str[10005];
int total=0;
scanf("%s",str);
int len=strlen(str);
char c='a';
_for(i,0,len) {
int minl=min(//寻找最短方向
26-abs(c-str[i]),
abs(c-str[i])
);
total+=minl;
c=str[i];//寻找下一个
}
printf("%d\n",total);
return 0;
}
B - 咕咕东想吃饭
咕咕东考试周开始了,考试周一共有n天。他不想考试周这么累,于是打算每天都吃顿好的。他决定每天都吃生煎,咕咕东每天需要买 i 个生煎。但是生煎店为了刺激消费,只有两种购买方式:
①在某一天一次性买两个生煎。
②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。
没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买 i 个生煎。其中(1≤n≤100000)(1≤i≤10000)
Input1
4
1 2 1 2
Output1
YES
Input2
3
1 0 1
Output2
NO
思路
我一开始是好几个嵌套if else,试图模仿所有情况,后来才发现原来这道题是有规律的:
- 每天用掉昨天的劵
- 若还剩偶数个生煎,可以用一次购买两个的方式将他们解决掉
- 若还剩奇数个生煎,则可以用多次买两个和一次买一煎一劵的方式
剩余券数=剩余生煎个数%2
这样的话题目完全不需要嵌套的逻辑判断,只需要进行简单的加减和取整。
心得
- 读题读题读题,圈起来圈起来圈起来。这道题我写了十几个测试用例,最后还是没有AC。后来忽然想通了,每天可以不止买一次!一开始我注意到两种购买方式可以用无数次了,但后面题目举的例子让我以为一天只能购买一次…读题什么的…
- 三道题里面,这道题最让我大开眼界,一个比较复杂的题目通过分析转化就变得很简单,amazing。
- 都说要先设计测试用例再编码,可这测试用例怎么设计呢?我绞尽脑汁设计了十几条测试用例,效率太低了,设计测试用例的套路是我要关心的一个点,应当开个笔记专门收集。
代码
#include <cstdio>
#define _for(i,s,e) for(int i=s;i<e;i++)
int a[100005];//大数据保存在全局区
int main() {
int n;
scanf("%d",&n);
bool ok = true;
int last = 0;//券数量,只有1/0两种可能
_for(i,0,n) {
scanf("%d",&a[i]);
//如果某日没把昨天剩下的生煎劵吃光,那就失败了
if(a[i]-last<0) {
ok = false;
break;
}
//每天用掉昨天的劵,
//若还剩偶数个生煎,可以用一次购买两个的方式将他们解决掉
//若还剩奇数个生煎,则可以用多次买两个和一次买一煎一劵的方式
//因此这里对2整除
last = (a[i]-last)%2;
}
//若过程中就失败,或过程顺利但最后一天剩劵了,则失败
if(last!=0 || ok==false){
printf("NO\n");
}else{//若每一天都成功且最后不剩券,则成功
printf("YES\n");
}
return 0;
}
C - 可怕的宇宙射线
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着-种叫做苟狗的生物, 这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n次,每次分裂后会在分裂方向前进a个单位长度。现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生zjm那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击”。
Input
4
4 2 3 2
Output
39
思路
BFS+多条件剪枝+{1,0,-1}数组表示接下来的走向+set去重
- 主要使用BFS思想作为题目框架,每个点可延伸出两个方向,不要忘记每次步长是不确定的。
- 同点同方向同深度可以被剪枝,否则不能剪。
- 可以用整数标识方向,数组来获取特定方向上的dx,dy。一开始我用map来获取方向,性能低且可读性差。
- set去重。
这道题我一开始是单纯用BFS去做,当然并不能做出来。
后来知道了剪枝
再后来发现可以用数组表示走向(一开始是用map做的映射)
再后来发现全局数组开大也会MLE
小小题目蕴含大千世界😂
心得
- 周三下午课重叠,我得先验收完OS课设再去打比赛,所以做题只剩下一小时,所以只是匆匆做完前两题,第三题都没来得及研究。
一半的时间拿到一半的分,应该还可以吧,虽然拿到的只是简单的那一半分(笑) - 学习通过静态数组保存分支,这样挺方便。
- 一开始忘记走过的路径都可以被炸了,只写了炸终点的。
- 很关键的一点是要剪枝,暴力搞不定这道题目。
- 有种说法是对称剪枝,不过我觉得对称判断挺麻烦的(可能有什么奇淫技巧吧不过我没见过emmm)
- 我对 同位置同方向同层次进行剪枝 就可以AC,不同层次不可剪枝,因为不同层次步长不同。
- 刚碰到“剪枝”吓坏了,寻思这又是什么新知识,一看代码发现原来是个
if(visited)return;
- 这道题要考虑的东西太多,脑子一团浆糊,做对了都理不太清思路(((m-__-)m
- 数组开大亿点点就MLE…,开小亿点点就AC,我又明白了,内存限制指的是总内存,而不单单指堆和栈。
代码
#include<bits/stdc++.h>
#define _for(i,s,e) for(int i=s;i<e;i++)
using namespace std;
struct point {
int x;
int y;
bool operator<(const point& a) const {
return x != a.x ? x < a.x : y < a.y;
}
};
int n;
int a[50];
set<point> total;
bool visited[400][400][35][8]= {false};
int nextx[]= {0,1,1,1,0,-1,-1,-1};
int nexty[]= {1,1,0,-1,-1,-1,0,1};
void bfs(point p,int times,int dir) {
if(times==n)return;
int x=p.x;
int y=p.y;
//完全相同的访问则剪枝
if(visited[p.x][p.y][times][dir])return;
visited[p.x][p.y][times][dir]=true;
//前进ai次
for(int i=0; i<a[times]; i++) {
total.insert({
p.x+=nextx[dir],
p.y+=nexty[dir]
});
}
//广搜
bfs({p.x,p.y},times+1,(dir+7)%8);
bfs({p.x,p.y},times+1,(dir+1)%8);
}
int main() {
scanf("%d",&n);
_for(i,0,n) {
scanf("%d",&a[i]);
}
bfs({200,200},0,0);
printf("%d",total.size());
return 0;
}
后记
我想起了一位老同学提到的“小圈子化”问题,圈内的人们疯狂玩梗,圈外人一脸懵逼。
就像算法竞赛,各种技巧和注意点,打算法竞赛的同学谈论起来游刃有余,可是圈外人就啥都听不懂,甚至看题解都懵。
我就纳闷了,二次元好歹还有个萌娘百科,算法竞赛的各种专有名词为啥没有个百科呢,每次遇到新东西只能在屈指可数的博文中寻找蛛丝马迹来理解它们。这知识领域有点…难洞悉啊,还好这门课能让我略微了解些皮毛。
经过寒假以及最近这段时间的学习,我发现算法这块没什么手册之类的东西,也没有什么全面的教材,唯一的学习方法就是刷题和看题解,甚至说看题解比刷题更重要。