16年决赛的真题,个人见解,有不足之处还望指点。(后面两题,我尽量补上)
第一题:一步之遥
从昏迷中醒来,小明发现自己被关在X星球的废矿车里。 矿车停在平直的废弃的轨道上。 他的面前是两个按钮,分别写着“F”和“B”。
小明突然记起来,这两个按钮可以控制矿车在轨道上前进和后退。 按F,会前进97米。按B会后退127米。
透过昏暗的灯光,小明看到自己前方1米远正好有个监控探头。 他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。 或许,通过多次操作F和B可以办到。
矿车上的动力已经不太足,黄色的警示灯在默默闪烁… 每次进行 F 或 B 操作都会消耗一定的能量。
小明飞快地计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。
请填写为了达成目标,最少需要操作的次数。
注意,需要提交的是一个整数,不要填写任何无关内容(比如:解释说明等)
解题思路:列一个方程即可:x*97-y*127=1,两重循环即可求出
答案:97
#include <stdio.h>
int main(void)
{
int i=0,j=0;
int sum=0;
for(i=1;i<200;i++)
{
for(j=1;j<200;j++)
{
if(i*97-j*127==1)
{
printf("%d",i+j);
return;
}
}
}
return 0;
}
第二题:凑平方数
把0~9这10个数字,分成多个组,每个组恰好是一个平方数,这是能够办到的。 比如:0, 36, 5948721
再比如: 1098524736 1, 25, 6390784 0, 4, 289, 15376 等等…
注意,0可以作为独立的数字,但不能作为多位数字的开始。 分组时,必须用完所有的数字,不能重复,不能遗漏。
如果不计较小组内数据的先后顺序,请问有多少种不同的分组方案?
注意:需要提交的是一个整数,不要填写多余内容。
解题思路:有题可知,最大数为9876543210,对其开平方为99380,我们可以先把0~99280之间,数的平方没有重复数字的数存储起来(存储的是平方),然后进行深度搜索。这里可以将每个组中的所有数字都存储在string类型中(操作方便)
参考答案:300
#include <stdio.h>
#include <iostream>
#include <string>
#include <algorithm>
#include <math.h>
using namespace std;
#define MAX 99380 //对987654321开二次方根
string toString(long long int num);
int judge(string num);
void init();
void search(int start,string number);
long long int NumSaved[MAX]; //NumSaved数组用于保存0~MAX中符合条件的平方数
int NumLength=0; //记录NumSaved中存储数据的个数
int Sum=0;
int main(void)
{
init();
// for(int i=0;i<300;i++) cout<<NumSaved[i]<<endl; //测试数据
search(0,"");
cout<<Sum;
/* for(int i=0;i<100;i++) //测试数据
cout<<toString(i*i)<<endl;*/
return 0;
}
//搜寻
void search(int start,string number)
{
int j = judge(number);
if(!j) return;
if(j==11) //judge中j初始化为1,所以这里是等于11
{
Sum++;
// cout<<number<<endl; //输出查看
return;
}
for(int i=start;i<NumLength;i++)
search(i+1,number+toString(NumSaved[i]));
}
//判断字符串num中是否含有重复数字
int judge(string num)
{
int count[10]={0},i=0,j=1; //因为search函数刚开始时num=“”,不能让它直接返回,所以初始化为1
int len = num.length();
for(i=0;i<len;i++)
{
if(num[i]=='.') continue;
int tmp = num[i]-'0';
count[tmp]++;
if(count[tmp]>1) return 0;
j++;
}
return j; //返回
}
//将数字转换为字符串
string toString(long long int num) //错误发生地
{
string result = "";
int tmp = num;
if(num==0) result += "0";
while(num!=0)
{
char tmp = num%10+'0';
result.insert(0,1,tmp); //将字符tmp存放在result字符串数组的0处
num/=10;
}
result.insert(0,1,'.'); //加'.'便于查错分析
return result;
}
//初始化,用NumSaved数组记录0~MAX中平方数没有重复数字的数字
void init()
{
long long int i=0;
for(i=0;i<MAX;i++)
{
long long int num = i*i;
if(judge(toString(num))) NumSaved[NumLength++] = num;
}
}
第三题:棋子换位
有n个棋子A,n个棋子B,在棋盘上排成一行。 它们中间隔着一个空位,用“.”表示,比如:
AAA.BBB
现在需要所有的A棋子和B棋子交换位置。 移动棋子的规则是:
1. A棋子只能往右边移动,B棋子只能往左边移动。
2. 每个棋子可以移动到相邻的空位。
3. 每个棋子可以跳过相异的一个棋子落入空位(A跳过B或者B跳过A)。
AAA.BBB 可以走法:
移动A ==> AA.ABBB
移动B ==> AAAB.BB
跳走的例子: AA.ABBB ==> AABA.BB
以下的程序完成了AB换位的功能,请仔细阅读分析源码,填写划线部分缺失的内容
源码如下:
#include <stdio.h>
#include <string.h>
void move(char* data, int from, int to)
{
data[to] = data[from];
data[from] = '.';
}
int valid(char* data, int k)
{
if(k<0 || k>=strlen(data)) return 0;
return 1;
}
void f(char* data)
{
int i;
int tag;
int dd = 0; // 移动方向
while(1){
tag = 0;
for(i=0; i<strlen(data); i++){
if(data[i]=='.') continue;
if(data[i]=='A') dd = 1;
if(data[i]=='B') dd = -1;
if(valid(data, i+dd) && valid(data,i+dd+dd)
&& data[i+dd]!=data[i] && data[i+dd+dd]=='.'){
//如果能跳...
move(data, i, i+dd+dd);
printf("%s\n", data);
tag = 1;
break;
}
}
if(tag) continue;
for(i=0; i<strlen(data); i++){
if(data[i]=='.') continue;
if(data[i]=='A') dd = 1;
if(data[i]=='B') dd = -1;
if(valid(data, i+dd) && data[i+dd]=='.'){
// 如果能移动...
if( ______________________ ) continue; //填空位置
move(data, i, i+dd);
printf("%s\n", data);
tag = 1;
break;
}
}
if(tag==0) break;
}
}
int main()
{
char data[] = "AAA.BBB";
f(data);
return 0;
}
解题思路:可以先把填空位置注释掉,多用几个例子("AA.BB","AAAA.BBBB")查一查错误的共性,最后可以发现如果移动位前后字符一样,则不能移动。例如AABA.BB,此时A前后都是B,则不能移动,只能向左移动B.
#include <stdio.h>
#include <string.h>
//从from位置移动到to位置,to位置赋值为from位置的值,from位置设置为空,赋值为'.'
void move(char* data, int from, int to)
{
data[to] = data[from];
data[from] = '.';
}
//k值边界检查,在边界内返回1,出边界返回0
int valid(char* data, int k)
{
if(k<0 || k>=strlen(data)) return 0;
return 1;
}
void f(char* data)
{
int i;
int tag; //此次移动是否成功
int dd = 0; // 移动方向
while(1){
tag = 0;
for(i=0; i<strlen(data); i++){
if(data[i]=='.') continue;
if(data[i]=='A') dd = 1; //dd为1,向右
if(data[i]=='B') dd = -1; //dd为-1,向左
//跳过相异的一个棋子落入空位,一次
if(valid(data, i+dd) && valid(data,i+dd+dd) //边界检查
&& data[i+dd]!=data[i] && data[i+dd+dd]=='.'){
//如果能跳...
move(data, i, i+dd+dd);
printf("1 %s\n", data);
tag = 1;
break;
}
}
//跳棋成功后,继续尝试跳棋
if(tag) continue;
for(i=0; i<strlen(data); i++){
if(data[i]=='.') continue;
if(data[i]=='A') dd = 1;
if(data[i]=='B') dd = -1;
if(valid(data, i+dd) && data[i+dd]=='.'){
// 如果能移动...
if(valid(data,i-dd)&&valid(data,i+dd+dd)&&data[i-dd]!=data[i]&&data[i+dd+dd]!=data[i]) continue; //填空位置
move(data, i, i+dd);
printf("2 %s\n", data);
tag = 1;
break;
}
}
if(tag==0) break;
}
}
int main()
{
char data[] = "AAAA.BBBB";
puts(data);
f(data);
return 0;
}
第四题:机器人塔
X星球的机器人表演拉拉队有两种服装,A和B。 他们这次表演的是搭机器人塔。
类似:
A
B B
A B A
A A B B
B B B A B
A B A B B A
队内的组塔规则是:
A 只能站在 AA 或 BB 的肩上。 B 只能站在 AB 或 BA 的肩上。
你的任务是帮助拉拉队计算一下,在给定A与B的人数时,可以组成多少种花样的塔。
输入一行两个整数 M 和 N,空格分开(0<M,N<500),分别表示A、B的人数,保证人数合理性。
要求输出一个整数,表示可以产生的花样种数。
例如:
用户输入:
1 2
程序应该输出:
3
再例如:
用户输入:
3 3
程序应该输出:
4
解题思路:根据输入提供的m,n可以率先确认底层机器人的个数,底层机器人的排列顺序确定了,那么顶层的机器人分布就确定了,这题的普遍思路是深度搜索(可能会超时),我又想了一种思路:既然机器人之后两种状况,那么我可以用二进制数来表示机器人的排列顺序,每一个排列对应一个数,可以减少搜索的时间。两种思路代码都在下面
深度搜索
#include <stdio.h>
#define MAX 45
int getBottom(int n,int m);
void setBottom(int m,int n,int cur,int bottom,int squery[][MAX]);
int count(int bottom,int squery[MAX][MAX],int m,int n);
int M=0,N=0;
int Sum=0;
int main(void)
{
int i=0,j=0,k=0,num=0;
int bottom;
int squery[MAX][MAX]={0};
scanf("%d %d",&M,&N);
bottom = getBottom(N,M);
setBottom(0,0,0,bottom-1,squery);
printf("%d",Sum);
return 0;
}
//求出最下面一层所有可能
void setBottom(int m,int n,int cur,int bottom,int squery[][MAX])
{
if(m>M||n>N) return;
if(cur==bottom)
{
if(count(bottom,squery,m,n))
{
Sum++;
int i=0,j=0;
for(i=bottom;i>=0;i--)
{
for(j=0;j<bottom-i;j++)
{
if(squery[i][j]==-1) printf("%-3c",'A');
else if(squery[i][j]==1) printf("%-3c",'B');
}
printf("\n");
}
}
printf("\n");
return;
}
squery[0][cur] = -1; //A为-1
setBottom(m+1,n,cur+1,bottom,squery);
squery[0][cur] = 1; //B为1
setBottom(m,n+1,cur+1,bottom,squery);
}
//由于最下面一层确定,则整体确定,对A和B计数
int count(int bottom,int squery[MAX][MAX],int m,int n)
{
int i=0,j=0,k=0;
for(i=1;i<bottom;i++)
{
for(j=0;j<bottom-i;j++)
{
if(squery[i-1][j]==squery[i-1][j+1])
{
squery[i][j] = -1;
m++;
}
if(squery[i-1][j]!=squery[i-1][j+1])
{
squery[i][j] = 1;
n++;
}
if(m>M||n>N) return 0; //如果大于总数,则返回0
}
}
return 1;
}
//计算出最下面一层的最大个数
int getBottom(int n,int m)
{
int limit = n+m;
int i=0,sum=0;
for(i=1;i<limit;i++)
{
sum+=i;
if(sum>limit) break;
}
return i;
}
二进制表示法
#include <stdio.h>
#include <math.h>
#define MAX 45
int getBottom(int n,int m);
void build(int bottom);
int M=0,N=0;
int Sum=0;
int main(void)
{
int i=0,j=0,k=0,num=0;
int bottom;
scanf("%d %d",&M,&N);
bottom = getBottom(N,M)-1;
build(bottom);
printf("%d",Sum);
return 0;
}
void build(int bottom)
{
int i=0,j=0,k=0;
int m=0,n=0; //二进制中0代表A,1代表B
int num=0,pos=1,end=0;
for(i=0;i<(1<<bottom);i++)
{
j = i; //记录每一层的数量
num = 0;
m=0,n=0;
k = 0; //记录二进制移位的次数
pos = 1;
end = bottom;
{
if(j&1) n++; //n代表B,代表1
else m++; //m代表A,代表0
}
while(k<end)
{
int tmp = j&3;
//计算上一层二进制的数
if(tmp==1||tmp==2) num = pos+num;
pos *= 2;
//对这一层A,B数目计数
if(tmp==0||tmp==1) m++;
else n++;
if(n>N||m>M) break;
j = j>>1;
k++;
if(k==end-1) //进入上一层
{
j = num;
num=0;
pos = 1;
k=0;
end--;
if(j&1) n++;
else m++;
if(n>N||m>M) break;
if(end==1)
{
Sum++;
break;
}
}
}
}
}
//计算出最下面一层的最大个数
int getBottom(int n,int m)
{
int limit = n+m;
int i=0,sum=0;
for(i=1;i<limit;i++)
{
sum+=i;
if(sum>limit) break;
}
return i;
}