文章目录
A 咕咕东的奇遇
1. 题目大意
咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。
输入:
输入只有一行,是一个字符串。
输出:
输出最少要转的次数。
样例:
zeus
18
2. 思路历程
- 这道题就是计算字符串中每两个相邻字符间的最小距离和,因此要把字母都转换成数字。
- 圆环旋转方向可顺时针可逆时针,因此每次取两个方向的最小距离即可。
3. 具体实现
- a用于记录输入的 字符串,pre用于记录上一个字符,now用于记录当前的字符,res用于记录距离和。
- 遍历字符串中所有字符,更新pre为now,now为当前处理的a[i](转换为数字是a[i] - ‘a’)。
- 两个方向的距离分别是abs(pre-now)和26-abs(pre-now),二者取最小。
4. 代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
using namespace std;
int main()
{
string a;
cin >> a;
int pre = 0, now = 0, res = 0;
for (int i = 0; i < a.size(); i++)
{
pre = now;
now = a[i] - 'a';
res += min(abs(pre-now), 26-abs(pre-now));
}
cout << res;
return 0;
}
B 咕咕东想吃饭
1. 题目大意
咕咕东考试周开始了,考试周一共有n天。他决定每天都吃生煎,咕咕东每天需要买 ai 个生煎。但是生煎店为了刺激消费,只有两种购买方式:①在某一天一次性买两个生煎。②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买 ai 个生煎。
输入:
输入两行,第一行输入一个正整数n(1<=n<=100000)表示考试周的天数。
第二行有n个数,第 i 个数 ai(0<=ai<=10000)表示第 i 天咕咕东要买的生煎的数量。
输出:
如果可以满足咕咕东奇怪的要求,输出"YES",如果不能满足,输出“NO”。(输出不带引号)
样例:
4
1 2 1 2
YES
2. 思路历程
- 刚开始我把题目想得太复杂了,对于每天的购买方式是一个一个考虑的,用了dfs。
- 每次搜索只进行一次购买,即只产生对今天可购买数-2或今明两天可购买数各-1的变化,只有当搜索到最后一天且可购买数刚好为0时成功。
- 这种方法对于100000天就要搜索100000的不知道几次方次,时间复杂度实在可怕,但是我比赛的两小时里竟然还在剪枝硬啃…
- 后来想到其实无需考虑中间那些天具体的购买方法,只需考虑最后一天是否有券剩下即可。
- 两种方法在本质上每天购买的数量都是2,需要偶数个的天数直接采用方案1(对后面无影响),需要奇数个的天数则用一次方案2(对后一天的个数-1)
- 最后判断最后一天需要的个数,为偶数则可行(全部采用方案1),为奇数则不可行(采用一次方案2但是剩下券)
- 其实刚开始有想到奇偶数,但是在比赛中害怕临时换掉整个思路会很耗时,现在知道坚持错误的方向才是最耗时的(还很难对),对于复杂度高到离谱的方法要先反思自己的思路而不是马上去剪枝。
3. 具体实现
- 数组a[i]用于记录每天的购买数量。
- 遍历数组
- 若a[i]<0,说明今天用不完明天的券,直接跳出循环
- 若a[i]>=0且为奇数时,采用方案2,明天中有一个用券购买,a[i+1]-1
- 循环结束判断最后一天所需个数的奇偶性。
4. 代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
using namespace std;
int n;
int a[100111];
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n - 1; i++)
{
if (a[i] < 0)
break;
if (a[i] % 2)
a[i+1]--;
}
if (a[n-1] % 2) cout << "NO";
else cout << "YES";
return 0;
}
C 可怕的宇宙射线
1. 题目大意
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n 次,每次分裂后会在分裂方向前进 ai个单位长度。
现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"。
输入:
输入第一行包含一个正整数n(n<=30) ,表示宇宙射线会分裂n次。
第二行包含n个正整数a1,a2…an,第 i 个数 ai 表示第 i 次分裂的宇宙射线会在它原方向上继续走多少个单位长度。
输出:
输出一个数 ans,表示有多少个位置会被降智打击。
4
4 2 2 3
39
2. 思路历程
- 刚开始思路错误,看到路径由一点层层扩散出去,先考虑了bfs
- 每一级搜索时,在队列中加入该级路径所有的终点,并将路径上的点加入集合,最后集合容量为路径长度
- 这个方法需要遍历每一条路径,时间复杂度高,而且bfs需使用队列,而防止已到达处重复计算需使用集合,相当于每条路径的终点存储两遍,空间复杂度也高,是非常不合适的做法。
- 后来换了个方向看路径的变化,即把扩散改为用延伸理解,考虑到了dfs
-
每次递归将路径上所有的点加入set(免去判断是否到达),但是这样进行指数级递归时间太长,需要优化。
-
由于这道题的扩散是重复的延伸,“每次向该方向的左右45°方向分裂”,因此可以只对最右的那条路进行dfs,其左边的那条以当前的主方向为轴进行对称
-
八个方向有四种对称方式,其中两个只改变x坐标或只改变y坐标的很简单,因此考虑两种斜线的对称 (还是很复杂的,我特地笔算了)
-
每次递归加入对称点后还要加入主路上的点。
3. 具体实现
- 所有路径的延伸有八个方向,用move_x和move_y数组分别表示x,y在八个方向延伸的坐标变化。
- 创建Point结构体用于记录位置,并进行操作符重载用于插入set。
- 初始点设为(0, -1)进入dfs(方格的坐标和点的坐标还是有点不一样的,用-1是为了和点坐标同步,对称的时候不糊涂)
- 函数dfs(int now, int i, int move)
- now用于记录当前起点(上一层递归的终点),i用于记录递归的层数,move用于记录当前路径延伸的方向
- 若未到达底层,则先进入下一层递归,传入起点next(now.x + move_x[move] * a[i], now.y + move_y[move] * a[I]),层数i+1, 方向(move + 1) % 8
- 对于每个已经在set中的点都要进行对称再加入set,因此用迭代器遍历set,对八个不同方向进行四种不同的对称方法
- 最后把当前主路径上的所有点加入set,这些点的坐标可以通过1到a[i]长度乘以当前方向在move_x和move_y上的取值得到sym(now.x + move_x[move] * j, now.y + move_y[move] * j)
- 最后set的容量即为走过的路径长度。
4. 代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
#include <set>
using namespace std;
int move_x[] = {0, 1, 1, 1, 0, -1, -1, -1};
int move_y[] = {1, 1, 0, -1, -1, -1, 0, 1};
int n, a[33];
struct Point
{
int x, y;
Point() {}
Point(int _x, int _y)
{
x = _x;
y = _y;
}
bool operator < (const Point& point) const
{
if (x != point.x) return x < point.x;
return y < point.y;
}
};
set<Point> points;
void dfs(Point now, int i, int move)
{
if (i == n)
return;
Point next(now.x + move_x[move] * a[i], now.y + move_y[move] * a[i]);
dfs(next, i + 1, (move + 1) % 8);
for (set<Point>::iterator it = points.begin(); it != points.end(); it++)
{
if (move == 0 || move == 4)
{
Point sym(now.x - (it->x - now.x), it->y);
points.insert(sym);
}
else if (move == 1 || move == 5)
{
Point sym(it->y - now.y + now.x, it->x + now.y - now.x);
points.insert(sym);
}
else if (move == 2 || move == 6)
{
Point sym(it->x, now.y + (now.y - it->y));
points.insert(sym);
}
else if (move == 3 || move == 7)
{
Point sym(now.x + now.y - it->y, now.x + now.y - it->x);
points.insert(sym);
}
}
for (int j = 1; j <= a[i]; j++)
{
Point sym(now.x + move_x[move] * j, now.y + move_y[move] * j);
points.insert(sym);
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
Point now(0, -1);
dfs(now, 0, 0);
cout << points.size();
return 0;
}