A-咕咕东的奇遇
题目描述
咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。
咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。
输入格式
输入只有一行,是一个字符串。
输出格式
输出最少要转的次数。
样例输入
zeus
样例输出
18
数据点 | 字符串长度 |
---|---|
1,2 | 小于等于10 |
3,4,5 | 小于等于100 |
6,7,8,9,10 | 小于等于10000 |
题解
大致题意:通过转动圆环让指针按顺序指完字符串中所有字母,并使转动圆环的次数最少。
转动圆环只有两种方向选择:顺时针和逆时针,那么问题就在于每次如何选择转动方向。
通过多次举例画图、总结数学公式可以发现,无论是逆时针转还是顺时针转,最短的路径不会超过12,一旦超过了12那么用反方向转的结果一定更优。
所以将相邻字母对应的ASCII码相减取绝对值,如果<=12,那么该绝对值就是这次要转动的次数;如果>12,选择反方向转动,需要用26减去该绝对值(举几个例子画画图总结就有了)。
完整代码
#include <iostream>
#include <string.h>
#include <cstring>
#include <cmath>
using namespace std;
int main()
{
string s;
int n;
cin>>s;
n = s.size();
int f = 1;
int ans = 0;
for(int i=0;i<n;i++)
{
int k = abs(s[i]-96-f);
if(k<=12) ans+=k;
else if(k>=13) ans+=(26-k);
f = s[i]-96;
}
cout<<ans<<endl;
return 0;
}
总结
这一题不难,重点是读懂题意,然后关注字母差值和转动次数之间的数学关系就行了,总结规律是关键。
B-咕咕东想吃饭
题目描述
咕咕东考试周开始了,考试周一共有n天。他不想考试周这么累,于是打算每天都吃顿好的。他决定每天都吃生煎,咕咕东每天需要买aia_iai个生煎。但是生煎店为了刺激消费,只有两种购买方式:
①在某一天一次性买两个生煎。
②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。
没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买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
题解
咕咕东买生煎一共只有两种购买方式:
①在某一天一次性买两个生煎。
②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。
所以每天吃的生煎来源于前一天买的券,和当天一次性两个两个买的。
在遍历每一天的过程中,如果用不完前一天的券那么就不能做到咕咕东的要求。在最后一天若还有券剩余也达不到要求。
完整代码
#include <iostream>
using namespace std;
int n;
int a[100100];
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int k = 0;
int quan = 0;
for(int i=0;i<n;i++)
{
if(a[i]-quan<0)
{
k = 1;
break;
}
quan = (a[i]-quan)%2;
}
if(quan!=0) k = 1;
if(k==0) cout<<"YES"<<endl;
else if(k==1) cout<<"NO"<<endl;
return 0;
}
总结
读懂题意后这题并不难,但是不细心看清楚题目就可能翻车了QAQ。在比赛的时候这题只拿了70分,,,,就是因为误读了题意,当时以为每天只能选择一个方案使用,只可以买0个、1个或者2个,后来才知道方案可重复什么的,痛失30分qwq。
C - 可怕的宇宙射线
题目描述
题解
折磨我一晚错误提交十几次的题目hhhhhh
这一题乍一看就想到用bfs搜索,遍历看到达了哪些点,然后做上标记计数。。。虽说题目给出分裂次数不超过30次,但是每次要分裂成两个方向,这可是一个指数级问题,要炸了qwq。
所以一定不能纯bfs,需要剪枝。想一下这个分裂移动的过程,搜索到每一层每个终点都要分裂成两个方向,以给出的步长移动,有没有什么情况是重复可以剪掉避免重复搜索的呢?
显然,并不是搜到相同位置的点就可以去掉了,因为即使在同一个位置还可能有八个不同的方向,方向不同,分裂的方向就不相同。
但是即便是同一位置并且方向相同的情况也不可以剪掉,因为这只能保证分裂后方向一致,而每一层分裂后要移动的步长并不一定相等,这会导致不同的结果。
所以,同一位置、方向相同并且在同一层分裂才属于重复可剪去的情况。
这一题的关键点就在剪枝这里,如果不选择性的去除重复情况,数据一大就会超空间。看样例这种小数据看不出来什么,这是指数增长的问题。仔细一想就会发现数据一大,前面提到的剪枝要剪掉的情况出现次数并不少,同位置同方向且在同一层分裂,剪去会节省很多空间和时间。
剪枝想清楚思路理好,就写bfs好了,在push进队列前加判断条件,代码就不多解释了。
完整代码
#include <iostream>
#include <string.h>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
using namespace std;
struct p
{
int x, y;
int cc;
int dir; // 1 2 3 4 上下左右 5 6 7 8
bool operator < (const p &o) const
{
if(x<o.x) return true;
if(y<o.y) return true;
if(cc<o.cc) return true;
if(dir<o.dir) return true;
}
};
queue<p> q;
bool jd[600][600];
p aa[600][600];
map<p,bool> mp;
int transx[9] = {0, 0, 0, -1, 1, -1, 1, -1, 1};
int transy[9] = {0, 1, -1, 0, 0, 1, 1, -1, -1};
int nextdir1[9] = {0, 5, 7, 5, 6, 1, 1, 3, 4};
int nextdir2[9] = {0, 6, 8, 7, 8, 3, 4, 2, 2};
int n, ff, rr;
int a[100];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
ff = 0;
rr = 0;
p first_p;
first_p.x = 300;
first_p.y = 300;
first_p.cc = 1;
first_p.dir = 1;
jd[first_p.x][first_p.y] = 1;
for(int g = 1;g<=a[1]-1;g++)
{
first_p.y +=1;
jd[first_p.x][first_p.y] = 1;
}
aa[first_p.x][first_p.y] = first_p;
q.push(first_p);
int kk = first_p.cc;
while(!q.empty())
{
p now_p = q.front();
q.pop();
p next_p1 = now_p;
p next_p2 = now_p;
next_p1.cc++;
next_p2.cc++;
next_p1.dir = nextdir1[now_p.dir];
next_p2.dir = nextdir2[now_p.dir];
if(next_p1.cc>n)
break;
for(int g=1;g<=a[next_p1.cc];g++)
{
next_p1.x += transx[next_p1.dir];
next_p1.y += transy[next_p1.dir];
jd[next_p1.x][next_p1.y] = 1;
next_p2.x += transx[next_p2.dir];
next_p2.y += transy[next_p2.dir];
jd[next_p2.x][next_p2.y] = 1;
}
if(!((aa[next_p1.x][next_p1.y].cc == next_p1.cc) && (aa[next_p1.x][next_p1.y].dir == next_p1.dir)))
{
q.push(next_p1);
aa[next_p1.x][next_p1.y] = next_p1;
}
if(!((aa[next_p2.x][next_p2.y].cc == next_p2.cc) && (aa[next_p2.x][next_p2.y].dir == next_p2.dir)))
{
q.push(next_p2);
aa[next_p2.x][next_p2.y] = next_p2;
}
}
int ans=0;
for(int i=0;i<600;i++)
for(int j=0;j<600;j++)
if(jd[i][j]==1)
ans++;
cout<<ans<<endl;
return 0;
}
总结
在想搜索问题的时候要想想是否可以剪枝,去掉重复搜索的情况,特别是数据范围大的题目。一开始写这题时就没有想全面,直接mle了,在想剪枝时也出现了很多错误,判断错了重复情况或者只去掉了一小部分,反复mle了很多次。然后想清楚去重后,使用了map查找判断给tle了qwq。。。菜菜摸了一晚之后终于给调出来了。。。码力欠费,一遇到复杂的问题就容易乱,很多时候就是不知道怎么写出来QAQ。