CSP-M1 补题 A-咕咕东的奇遇 B-咕咕东想吃饭 C-可怕的宇宙射线
A-咕咕东的奇遇
题目描述
咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。
输入输出格式及样例
Input
输入只有一行,是一个字符串。
Output
输出最少要转的次数。
Input
zeus
Output
18
数据规模
数据点 | 字符串长度 |
---|---|
1,2 | 小于等于10 |
3,4,5 | 小于等于100 |
- | …… |
6,7,8,9,10 | 小于等于10000 |
思路
本题的最少转动次数就是所给字符串中相邻两个字母之间的最短距离和,因为每当你旋转到下一个字母,下一次转动轮盘的起点也会随之变为上一次旋转的终点,所以此题可以迭代求出相邻两个字母之间的最短距离再求和就可。
实验代码
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
string s = { "abcdefghijklmnopqrstuvwxyz" };//表示轮盘的字符串
int fi(char a, char b) {
int an = s.find(a); int bn = s.find(b);//得到两个字母再字符串中的索引
if (abs(an - bn) < 13)return abs(an - bn);//两者之差小于13,及为最短距离
else return 26 - abs(an - bn);//两者之差大于26/2,最短距离极为从另一个方向前往的距离
}
int main() {
string test; cin >> test;
int n = test.size();
int sum = fi('a', test[0]);
for (int i = 1; i < n; i++) {
sum += fi(test[i - 1], test[i]);
}
cout << sum << endl;
return 0;
}
B-咕咕东想吃饭
题目描述
咕咕东考试周开始了,考试周一共有n天。他不想考试周这么累,于是打算每天都吃顿好的。他决定每天都吃生煎,咕咕东每天需要买 a i 个生煎。但是生煎店为了刺激消费,只有两种购买方式:①在某一天一次性买两个生煎。②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买 a i 个生煎。
输入输出格式及样例
Input
输入两行,第一行输入一个正整数n(1<=n<=100000)表示考试周的天数。
第二行有n个数,第i个数ai(0<=ai<=10000)表示第i天咕咕东要买的生煎的数量。
Output
如果可以满足咕咕东奇怪的要求,输出"YES",如果不能满足,输出“NO”。(输出不带引号)
Input
4
1 2 1 2
Output
YES
Input
3
1 0 1
Output
NO
时间及内存限制
每个测试点1000ms 262144kB。
思路
观察题目,每天有三种选择,不买,一次买两个和一次买一个并获得一个券,券可以让明天免费买一个,最主要的是最后一天不能剩券,为此我们应当尽可能的少使用第三种选择,因为券的使用期是明天,明天必须用完,但明天不一定需要这么多,而且购买次数是无限的,所以当天想买的生煎数只要不是单数都可以用第二种方式满足,不会给明天带来负担。
所以我们的思路是,当天要买的生煎数先减去手中的券数,券数清零,然后判断剩余量,剩余量为0则无需购买,单数则用一次第三种方式,其余都选第二种,然后券数加一,双数则全部选第二种。到最后一天判断能否满足条件以及中途判断剩余量是否小于零即可。
实验代码
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> p;
bool temp = false;
int main(){
cin >> n;
int t;
for (int i = 0; i < n; i++) {
cin >> t;
p.push_back(t);
}
int i = 0;
int count = 0;
while (i < n) {
int test = p[i];
test -= count;
if (test < 0)break;
count = 0;
if (test % 2 != 0)count++;//奇数说明当天还剩一个,用第二种方式购买,获得一张券
if (i == n - 1 && count != 0)break;//最后一天还剩有券,不能满足咕咕东的要求
i++;
}
if (i == n)temp = true;//循环的返回值要么等于n要么小于n,小于n表示无法满足,等于n能够满足
if (temp == true)cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}
C-可怕的宇宙射线
题目描述
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n 次,每次分裂后会在分裂方向前进 ai个单位长度。
现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"。
输入输出格式及样例
Input
输入第一行包含一个正整数n(n<=30) ,表示宇宙射线会分裂n次。
第二行包含n个正整数a1,a2…an,第 i个数ai 表示第 i次分裂的宇宙射线会在它原方向上继续走多少个单位长度。
Output
输出一个数 ans,表示有多少个位置会被降智打击。
Input
4
4 2 2 3
Oputput
39
样例描述如下:
思路
对于这种类似与迷宫问题的地图型,很容易想到bfs走迷宫的做法。而且是采用递归bfs的操作,因为分裂是指数级的增长,采用迭代会涉及到回溯的问题,写起来较为麻烦。
所以,常规做法是,建立两个数组作为每条射线前进的时候的横纵坐标编译量,一共八个方向,分别是
方向索引 | 方向 | 横坐标偏移量 | 纵坐标偏移量 |
---|---|---|---|
0 | 上 | 0 | 1 |
1 | 右上 | 1 | 1 |
2 | 右 | 1 | 0 |
3 | 右下 | 1 | -1 |
4 | 下 | 0 | -1 |
5 | 左下 | -1 | -1 |
6 | 左 | -1 | 0 |
7 | 左上 | -1 | 1 |
所以偏移量数组为
int dx[] = { 0,1,1,1,0,-1,-1,-1 };
int dy[] = { 1,1,0,-1,-1,-1,0,1 };
可以看到,对于每个方向 i ,产生分裂,分裂后的方向就是 i+1 和 i-1 (除0和7外),这里可以将两个方向单独处理,但为了方便,可以将他合并,由于八个方向是循环的,对于循环问题可以采用求余的方式 i+1 写为 (i+1)%8, i-1 写为 (i+7)%8,这样就实现了八个方向的统一。
于是开始递归bfs,对于每条射线,让其前进对应距离的过程中,将经过的点加入数组,到了分裂点就进行递归向两个方向分裂前进,最后对数组去重,输出其大小即可。
很明显,单纯的这种做法会导致超时,因为做了很多无用功,因为分裂产生不同的射线可能会因为不断的分裂最终与其他射线相交,同时如果行进方向和距离相等的话两个射线就和一条射线没有区别,不对此进行剪枝的话不仅会在递归的时候浪费时间,同时增加去重的时间,所以我们建立一个visit四维数组进行判定是否曾经有过一样的射线,如果有就不用进行此次递归了。
至于为什么是四维数组,起初,我没有建立数组,因为没有考虑到剪枝的问题,然后超时了之后进行剪枝,单纯的建立二维数组判定,结果没有用处,因为问题考虑得不全面,然后进一步考虑,建立三维数组,没有考虑分裂阶段的问题,因为以为同一分裂阶段不会有两条一样的射线只考虑传播方向,后面知道问题后才采用四维数组。
其次,起先,我是在射线直线传播过程中经过的每个点都先判断这个点是否已到达,没有就设置为已到达,然后入数组,因为想优化去重时间,但是出现严重错误,因为这样会导致,同一分裂阶段同一方向经过这个点开始分裂时,判定已经到达会导致跳过该递归分裂,但其实该射线没有出现过,因为传播距离不同,所以后面只是将分裂点置为已到达,就ac了。
对于去重,由于我使用的vector数组,开始不知道去重的方法,百度知道vector数组的去重方法有两种,一种是用set类型,另一种是使用sort和unique函数,我采用的是set类型去重。
总结
本题遇到问题很多,都是考虑不全导致,下次注意。
实验代码
#include <iostream>
#include <vector>
#include <set>//用来去重
using namespace std;
int n;//总分裂次数
int c[30];//每次分裂的前进距离,最多分裂30次
//用两个数组表示每个点的横纵坐标偏移量,从0到7分别表示 上 右上 右 右下 下 左下 左 左上
int dx[] = { 0,1,1,1,0,-1,-1,-1 };
int dy[] = { 1,1,0,-1,-1,-1,0,1 };
vector<pair<int, int> > q;
bool visit[1000][1000][30][8] = { false };//四维数组分别表示 横坐标 纵坐标 当前分裂阶段 以及前进方向 用于剪枝
void bfs(int x, int y, int count, int dir) {
if (count >= n)return;
if (visit[x][y][count][dir] == true)return;//表示已经在同一次分裂时间段在这个点进行过同方向上的分裂了,不需要再进行
visit[x][y][count][dir] = true;//置为true,便于剪枝
for (int i = 0; i < c[count]; i++) {
x += dx[dir]; y += dy[dir];
q.push_back({ x,y });
}
bfs(x, y, count + 1, (dir+7)%8);
bfs(x, y, count + 1, (dir+1)%8);//分裂方向上进行递归
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> c[i];
bfs(500, 500, 0, 0);
set<pair<int,int> > p(q.begin(), q.end());//去重
cout << p.size() << endl;
return 0;
}