程序设计思维与实践 CSP-M1

/* 我疯了 为什么这么多篇博客只有这篇说题目重复率较高 博客管理里就这篇前面有个惊叹号逼死强迫症啊啊啊 */

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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值