week4 周三下午13:30-15:30两小时的限时csp模拟,三题只做出了第一题。现复盘理清思路。
A - 咕咕东的奇遇
题目描述
咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。
输入格式
输入只有一行,是一个字符串。
输出格式
输出最少要转的次数。
样例输入
zeus
样例输出
18
数据点说明
数据点 | 字符串长度 |
---|---|
1,2 | 小于等于10 |
3,4,5 | 小于等于100 |
6,7,8,9,10 | 小于等于10000 |
示意图
分析
我是通过将各个char字符变成数字,即a为1,z为26等,分析后可以发现,两个数之间需要转的次数不会超过26/2=13,以a点和n点为方向,当两数之差<13时,通过n进行转动,次数即两数之差(n左边的数都大于右边的数),不然通过a进行转动,次数为26 - max(第一个点, 第二个点) + min(第一个点, 第二个点)。代码如下:
C语言
#include<algorithm>
#include <cstring>
#include<cstdio>
using namespace std;
int main() {
char a[10000];
int step = 0,first = 1;
scanf("%s", &a);
for (int i = 0;i < strlen(a); i++) {
int x = a[i] - 96;//转换为对应的数字
if (max(first, x)- min(first, x)>13)
step += 26 - max(first, x) + min(first, x);
else
step += max(first, x) - min(first, x);
first = x;//更新现在的位置为x
}
printf("%d",step);
return 0;
}
B - 咕咕东想吃饭
题目描述
咕咕东考试周开始了,考试周一共有n天。他不想考试周这么累,于是打算每天都吃顿好的。他决定每天都吃生煎,咕咕东每天需要买ai个生煎。但是生煎店为了刺激消费,只有两种购买方式:①在某一天一次性买两个生煎。②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买ai个生煎。
输入格式
输入两行,第一行输入一个正整数n(1<=n<=100000),表示考试周的天数。第二行有n个数,第i个数ai(0<=ai<=10000)表示第i天咕咕东要买的生煎的数量。
输出格式
如果可以满足咕咕东奇怪的要求,输出"YES",如果不能满足,输出“NO”。(输出不带引号)
样例输入 1
4
1 2 1 2
样例输出 1
YES
样例输入 2
3
1 0 1
样例输出 2
NO
数据点说明
数据点 | n(上限) | ai(上限) |
---|---|---|
1,2 | 10 | 10 |
3,4,5 | 1000 | 10 |
6,7 | 10 | 10000 |
8,9,10 | 100000 | 10000 |
分析
分析每天购买情况,不是奇数就是偶数,当为偶数时,要么是用第一种购买方式,一次买两个,此时手中没有券,券的状态不改变;要么是用第二种购买方式当天买了一个并用了昨天一张券,还有一张券留给明天,此时手中必有券,券的状态仍不改变。当购买数量为奇数时,要么是用第二种购买方式,买了一个留一张券给明天,此时券的状态改变,有无到有;要么是用第一种购买方法,买了两个再用掉了前一天留下的券,此时券的状态由有到无。
注意: 当a[i] == 0直接退出,输出NO,因为题目中有他决定每天都吃生煎!
C++
#include<iostream>
using namespace std;
int a[100010];
int main() {
int n;
cin >> n;
bool flag=false;//flase为没有票在手中
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
if (a[i]%2 == 0) {//为偶数时
if (a[i] == 0) {
flag = true;
break;//退出
}
//else {
//if (flag) { flag = true; continue; }//如果有票在手中,此时还是有票要给下一个
//else { flag = false; }//如果没票在手中,此时为偶数,下一次还是没票
//}
}
else {
//if (flag)//如果有票在手中,下一天没票剩余了
// flag = false;
//else//如果没票在手中,下一天必须得有票剩余
// flag = true;
flag = (!flag);//综合后即这句话
}
}
if (!flag)cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}
附上更加简洁高效的老师的代码。
C - 可怕的宇宙射线
时间与内存限制
每个测试点 1000ms 262144KB
题目描述
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天
生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁
人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的
左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂 次,每次分裂后会在分裂方向前进 个单位长度。
现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生 那么菜的水平,所以瑞神来请求你帮他计算出共有多
少个位置会被"降智打击"
输入描述
输入第一行包含一个正整数n(n<=30) ,表示宇宙射线会分裂n次。
第二行包含n个正整数a1,a2……an ,第i个数ai(ai<=5)表示第i次分裂的宇宙射线会在它原方向上继续走多少个单位长度。
输出描述
输出一个数 ,表示有多少个位置会被降智打击
样例输入
4
4 2 2 3
样例输出
39
数据点说明
数据点 | n |
---|---|
10% | <=10 |
40% | <=20 |
100% | <=30 |
示意图
下图描绘了样例中宇宙射线分裂的全过程,仅做参考。
分析
这题肯定要用到递归,每次在分裂到达的新的起点重新开始进行函数。但是如果递归不做任何处理,仅当到了分裂次数后退出,会超时。之前我用的就是二维数组,到过该点变为true,超时了。所以进行可行性剪枝,用到了四维数组,来记录起点的x,y坐标值,方向及第几次分裂,如果四项均相同,则是一样的情况,只要考虑一种情况就行(因为都到达同一个点只能算作一次),这是记忆化dfs,另外还可以用bfs来做。
至于为什么只要开300的数组,是因为30(最多分裂次数)*5(最多移动单位长度)*2(两边方向)= 300。
C++
#include<iostream>
using namespace std;
int n, ans=0;
int a[40];
bool flag[400][400] = { false };//记录网格中是否被标记过
bool check[400][400][35][8] = { false };//用四维数组记录情况,不让情况重复,否则容易超时
const int ddx[] = { 0,1,1,1,0,-1,-1,-1 };
const int ddy[] = { 1,1,0,-1,-1,-1,0,1 };
void dfs(int x,int y,int count, int pos)
{//x,y为坐标,count为现在进行的分裂次数,pos为方向
if (count>n|| check[x][y][count][pos])
return;//进行剪枝,达到了分裂次数后返回,同时如果起点相同,分裂次数相同,方向相同则为同一过程,则剪枝
check[x][y][count][pos] = true;
for (int i = 0; i < a[count]; i++)
{//在指定移动长度内遍历符合条件的个数
x += ddx[pos];
y += ddy[pos];
if (!flag[x][y])
{//如果未被标记过
ans++;
flag[x][y] = true;
}
}
dfs(x, y, count + 1, (pos + 7) % 8);//两个方向进行递归
dfs(x, y, count + 1, (pos + 1) % 8);
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
dfs(200, 200, 1, 0);
cout << ans << endl;
return 0;
}
提供的代码差不多。
另外,有些收益匪浅的一些csp注意点。以后要多注意分情况考虑不同测试点,避免爆零!最后30分钟左右主要用来检查,减少不必要的丢分!要多多考虑复杂度的问题,容易分析到最后复杂度太高从而得从头重新开始思考解决方法。
对于超时问题的一些对策:
- 先分析复杂度,如果复杂度正确则应优化常数,否则优化算法;
- 常数优化:cin换scanf、替换stl、甚至考虑计算机原理层面的优化(局部性原理、手动实现递归等)。