/* 我疯了 为什么这么多篇博客只有这篇说题目重复率较高 博客管理里就这篇前面有个惊叹号逼死强迫症啊啊啊 */
CSP-M1
A - 咕咕东的奇遇
题目:
输入:一个字符串
输出:最少需要转的次数
思路:
取模
就是一个取模的问题。
每一次转的时候,都是两个字符之间的比较。除第一个字符是与 ‘a’ 比较以外,其他的字符都是与后一个字符进行比较。因此用for循环实现累加,但要注意的是第一个字符需要另行与 ‘a’ 比较,而且for循环时只比较到倒数第二个字符。
对于每次需要转的最小次数,设计turn函数计算每次需要转的最小次数。设这两个字符为x,y,那么x,y的相对位置情况有两种:
而每次计算的转的次数就是以下两种情况的最小值:
第一种情况就是|x-y|,第二种情况是26-max(x,y)+min(x,y)。
总结:
1.三目运算符还是挺好用的
2.把一些功能函数封装,有利于理思路
3.注意事项
① 要有意识地添加头文件,abs()函数的头文件为 cmath 。
②要把问题本质从实际问题中抽象出来,而不是就实际问题背景而解决问题。模测的时候想的太多了,在第一题上花的时间显然过多。一是因为码力不足(需要反省),二是因为经验不足。在后面的学习上要一点点积累起来。
代码:
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
int turn(int x,int y)
{
int a=abs(x-y);
int b=x>y?26-x+y:26-y+x;
return a<b?a:b;
}
int main()
{
string s;
cin>>s;
int count=turn(0,s[0]-'a');
for(int i=0;i<s.size()-1;i++)
count+=turn(s[i]-'a',s[i+1]-'a');
cout<<count;
return 0;
}
B - 咕咕东的生煎
题目:
输入:第一行一个数字n,表示考试周的天数;第二行n个数字表示每天吃的生煎数。 输出:能实现则输出"YES",反之输出"NO"。思路:
等价
要注意到,如果以第二种购买方法购买n次,若n为偶数,可等价为一天以第一种购买方法购买n/2次,第二天同样以第一种购买方法购买n/2次;若n为奇数,则等价为一天以第一种方法购买(n-1)/2次后,以第二种购买方式购买一次。所以可等价为第二种购买方式最多购买一次。
所以除第一天以外,每天的生煎数可以由两个指标来判断,即吃的生煎数的奇偶性和前一天是否有券。
最后,由于不能有多余的券,所以对最后一天单独判断。
总结:
1.题意很重要
模测时题意理解错误,以为每天最多只能买2个生煎,但其实两种购买方式每天都可以用无数次【 那时候还奇怪生煎数怎么会有1000个 (╯‵皿′)╯︵┻━┻
一定一定要看清题目看懂题意再下手!
代码:
#include <iostream>
using namespace std;
int n;
int num[200000];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>num[i];
bool quan=0;
if(num[1]%2) //第一天偶数个
quan=0;
else //第一天奇数个
quan=1; //肯定有奇数张券
for(int i=2;i<n;i++) //最后一天另算
{
if(quan) //有券
{
if(num[i]%2==0) quan=1; //偶数
else quan=0;
}
else
{
if(num[i]%2==1) quan=1;
else quan=0;
}
}
//最后一天
if(quan)
{
if(num[n]%2==1) cout<<"NO";
else cout<<"YES";
}
else
{
if(num[n]%2==1) cout<<"YES";
else cout<<"NO";
}
return 0;
}
C - 可怕的宇宙射线
题目:
输入:第一行为一个正整数n(n <= 30),表示宇宙射线会分裂n次,第二行为n个正整数a1,a2…an,第i个数ai(ai <= 5)表示第i次分裂的宇宙射线会在它原方向上继续走多少个单位长度。
输出:ans,表示有多少个位置会被降智打击。
思路:
dfs/剪枝
如果直接dfs,因为最多30次分裂,2的30次方绝对tle,所以必须剪枝。
我先尝试着第一次分裂的时候,只记录一个方向,通过对称的式子直接将另半边的点标记了,但还是不行,毕竟除个2也有2的29次方(つД`)。
换思路。首先思考这样一个问题。假设!一个300×300的二维数组,每个点都是分裂点,最多30个不同的长度,一共有8个方向,那么!300×300×30×8,也才10的6次方都不到,和2的30次方完全没法比!所以!肯定有重复的分裂点,而且数据越大,重复的超多。所以!先把所有分裂的点和点的状态记下来。把重复的全部去掉。
分裂的点和点的状态包括坐标x,y,和要前进的长度(由于直接在dfs的时候取前进的长度不是很方便,所以只记录长度在length数组中的标号)和前进的方向。
在dfs的时候,顺表把经过的点用set保存下来,因为set自动去重。
其他的处理就很容易了。用常量数组记录每个方向的偏移量,再用两个常量数组记录每个方向的分裂点的分裂方向。
总结:
set结构自动去重。
通过学习大佬的代码终于ac了/(ㄒoㄒ)/~~。dfs的剪枝不是那么容易的事情啊。
代码:
#include <iostream>
#include <set>
using namespace std;
int n;
int length[50];
set< pair<int,int> > s;
bool p[400][400][50][9];
//左上 0,上 1,右上 2,右 3,右下 4,下 5,左下 6,左 7
const int dx[]={-1,-1,-1,0,1,1,1,0};
const int dy[]={-1,0,1,1,1,0,-1,-1};
//分裂后的两个方向
const int d1[]={7,0,3,2,3,4,7,0};
const int d2[]={1,2,1,4,5,6,5,6};
void dfs(int x,int y,int len,int pre) //位置,长度的标号,方向
{
if(len>n) //结束
return;
if(p[x][y][len][pre]) //已被记录
return;
p[x][y][len][pre]=1; //标记
for(int i=0;i<length[len];i++)
{
x=x+dx[pre]; //加偏移量
y=y+dy[pre];
s.insert({x,y});
}
dfs(x,y,len+1,d1[pre]); //d1[pre]为分裂后的一个方向
dfs(x,y,len+1,d2[pre]); //d2[pre]为分裂后的另一个方向
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>length[i];
dfs(150,150,1,1); //x,y,len 在记录长度的数组里的标号,pre 方向
cout<<s.size();
}