最近因为参加蓝桥杯的比赛,所以总结了下去年的题目。
题目都是比较具有研究价值的典型题目。透彻理解这些题,对编程能力的提高是有一定的帮助的。
建议大家有时间可以研究下。
第一题
煤球数目
有一堆煤球,堆成三角棱锥形。具体:
第一层放1个,
第二层3个(排列成三角形),
第三层6个(排列成三角形),
第四层10个(排列成三角形),
....
如果一共有100层,共有多少个煤球?
请填表示煤球总数目的数字。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
解析:典型的等差数列问题,需要将等差数列和作为一个数列求和。(数学功底好的话可以直接写出100*101*102/6=171700)
#include<iostream>
using namespace std;
int main(void){
int sum=0;
for(int i=1;i<=100;i++)
sum+=(1+i)*i/2;
cout << sum;
return 0;
}
答案:171700
第二题
生日蜡烛
某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。
现在算起来,他一共吹熄了236根蜡烛。
请问,他从多少岁开始过生日party的?
请填写他开始过生日party的年龄数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
解析:还是等差数列,与第一问类似,这回已知和,求解初值,因为是填空题,直接暴力循环求解争取时间即可。
#include<iostream>
using namespace std;
int main(void){
for(int i=1;i<=100;i++){
int sum=0;
for(int j=1;sum<=236;j++){
sum+=i+j-1;
if(sum==236){
cout << i;
return 0;
}
}
}
return 0;
}
答案:26
第三题
凑算式
B DEF
A + --- + ------- = 10
C GHI
(如果显示有问题,可以参见【图1.jpg】)
这个算式中A~I代表1~9的数字,不同的字母代表不同的数字。
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。
解析:从这道题开始难度有所增加,因为出现了除法所以需要定义浮点型的数据,同样暴力循环计数即可,从A开始填数,但要注意每个数不可与其他数重复以及bcd和ghi转化 三位数的问题。
当然可以写递归来缩短代码量和数组解决重复问题这里就不尝试了。
#include<iostream>
using namespace std;
int main(void) {
int a, b, c, d, e, f, g, h, i0;
int sum = 0;
for (int i = 1; i<10; i++) {
a = i;
for (int i = 1; i<10; i++) {
if (i == a)
continue;
b = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b)
continue;
c = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b || i == c)
continue;
d = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b || i == c || i == d)
continue;
e = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b || i == c || i == d || i == e)
continue;
f = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b || i == c || i == d || i == e || i == f)
continue;
g = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b || i == c || i == d || i == e || i == f || i == g)
continue;
h = i;
for (int i = 1; i<10; i++) {
if (i == a || i == b || i == c || i == d || i == e || i == f || i == g || i == h)
continue;
i0 = i;
if ((double)a + (double)b / (double)c + (double)(d * 100 + e * 10 + f) / (double)(g * 100 + h * 10 + i0) == 10) {
sum++;
}
}
}
}
}
}
}
}
}
}
cout << sum;
return 0;
}
答案:29
第四题
快速排序
排序在各种场合经常被用到。
快速排序是十分常用的高效率的算法。
其思想是:先选一个“标尺”,
用它把整个队列过一遍筛子,
以保证:其左边的元素都不大于它,其右边的元素都不小于它。
这样,排序问题就被分割为两个子区间。
再分别对子区间排序就可以了。
下面的代码是一种实现,请分析并填写划线部分缺少的代码。
#include <stdio.h>
void swap(int a[], int i, int j)
{
int t = a[i];
a[i] = a[j];
a[j] = t;
}
int partition(int a[], int p, int r)
{
int i = p;
int j = r + 1;
int x = a[p];
while(1){
while(i<r && a[++i]<x);
while(a[--j]>x);
if(i>=j) break;
swap(a,i,j);
}
______________________;
return j;
}
void quicksort(int a[], int p, int r)
{
if(p<r){
int q = partition(a,p,r);
quicksort(a,p,q-1);
quicksort(a,q+1,r);
}
}
int main()
{
int i;
int a[] = {5,13,6,24,2,8,19,27,6,12,1,17};
int N = 12;
quicksort(a, 0, N-1);
for(i=0; i<N; i++) printf("%d ", a[i]);
printf("\n");
return 0;
}
注意:只填写缺少的内容,不要书写任何题面已有代码或说明性文字。
解析:算法基础排序经典问题,需要收悉基本排序法方,因为没有出现嵌套循环所以一定是由递归实现的,partition即为核心语句,观察while(i<r && a[++i]<x);和while(a[--j]>x); 分别是选出了从前第一个大于或从后第一个小于x的数的下标,r为终止数下标,p则为起始数的下标,直到i>=j,完成一次后快速排序分为两部分,但是标尺的未知没有改 变,所以需要交换标尺的位置,因为j的选择比i的后执行所以与j交换。
答案:swap(a,p,j);
第五题
抽签
X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。
C国最多可以派出2人。
....
那么最终派往W星的观察团会有多少种国别的不同组合呢?
下面的程序解决了这个问题。
数组a[] 中既是每个国家可以派出的最多的名额。
程序执行结果为:
DEFFF
CEFFF
CDFFF
CDEFF
CCFFF
CCEFF
CCDFF
CCDEF
BEFFF
BDFFF
BDEFF
BCFFF
BCEFF
BCDFF
BCDEF
....
(以下省略,总共101行)
#include <stdio.h>
#define N 6
#define M 5
#define BUF 1024
void f(int a[], int k, int m, char b[])
{
int i,j;
if(k==N){
b[M] = 0;
if(m==0) printf("%s\n",b);
return;
}
for(i=0; i<=a[k]; i++){
for(j=0; j<i; j++) b[M-m+j] = k+'A';
______________________; //填空位置
}
}
int main()
{
int a[N] = {4,2,2,1,1,3};
char b[BUF];
f(a,0,M,b);
return 0;
}
仔细阅读代码,填写划线部分缺少的内容。
注意:不要填写任何已有内容或说明性文字。
解析:显然又是一个递归问题,注意题目的坑,这里不止存在ABC而是N定义的6个单位分别是ABCDEF,f(a,0,M,b)中a是数组,b[M] 是BUF缓冲区数据的,k由0到N可return, 表明k代表第k单位被选中,至于选中多少由for(i=0; i<=a[k]; i++)确定,m则由从初填写的5到0,执行输出,代表m为剩余可选中数目,可得出答案。
答案:f(a,k+1,m-i,b);
第六题
方格填数
如下的10个格子
+--+--+--+
| | | |
+--+--+--+--+
| | | | |
+--+--+--+--+
| | | |
+--+--+--+
(如果显示有问题,也可以参看【图1.jpg】)
填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
解析:涉及集合的问题非常值得分析。这道题没用给出0-9是否可以复用,因为不可以复用条件追加了1条,所以求得不可以复用可以得到可复用时的答案。
可以简化问题,把10个位置标号(这里从0开始),即可追踪位置,我们从左上角开始向右填写数字,会发现需要比较的全部方向分别时,左,左上,上,右上四个方 向,每个位置有存在有的方向不需要比较的情况这是可以总结初结论1245689向左,456789向上,5689向左上,345789向右上,使用循环解决。去除Repeat条件即得重 复结果。
using namespace std;
int map[10];
int sum = 0;
int x = 0;
int Repeat(int map[], int i, int j) {
for (int k = 0; k<i;k++) {
if (map[k] == j)
return 1;
}
return 0;
}
int NotMeetConditions(int map[], int i, int j) {
if (i == 1 || i == 2 || i == 4 || i == 5 || i == 6 || i == 8 || i == 9)
if (map[i - 1] == j + 1 || map[i - 1] == j - 1)
return 1;
if (i == 4 || i == 5 || i == 6 || i == 7 || i == 8 || i == 9)
if (map[i - 4] == j + 1 || map[i - 4] == j - 1)
return 1;
if (i == 5 || i == 6 || i == 8 || i == 9)
if (map[i - 5] == j + 1 || map[i - 5] == j - 1)
return 1;
if (i == 3 || i == 4 || i == 5 || i == 7 || i == 8 || i == 9)
if (map[i - 3] == j + 1 || map[i - 3] == j - 1)
return 1;
return 0;
}
void Fill(int map[10], int i, int* sum) {
if (i == 10) {
(*sum)++;
return;
}
for (int j = 0; j<10; j++) {
if (Repeat(map, i, j) || NotMeetConditions(map, i, j)) {
continue;
}
else
map[i] = j;
Fill(map, i + 1, sum);
}
}
int main(void) {
Fill(map, 0, &sum);
cout << "sum:"<<sum;
return 0;
}
答案:不重复:1580 重复:206059714
第七题
剪邮票
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
解析:与上一道题一样可以尝试循环解决,注意经可能不适用二维数组,不过这会我使用递归解决。选择1至12作为起点,选择下一个数使用相同方法判定,重复情况不需要考 虑选取顺序只有选取图形即组合问题,若不向上取(排除重复情况,3行为奇数不对称)则9至12不能作为起点(最多4个),又因为图形左右对称结果除以2即可过滤重 复情况。
#include<iostream>
using namespace std;
int i=1;// i=1~12
int buf[5];
int sum=0;
void function(int i,int* buf,int j,int* sum){
buf[j]=i;
if(j==4){
(*sum)++;
return;
}
if((i-1)%4==0);
else{
function(i-1,buf,j+1,sum);
}
if((i+1)%4==1);
else{
function(i+1,buf,j+1,sum);
}
if(i+4>=13);
else{
function(i+4,buf,j+1,sum);
}
}
int main(void)
{
for(int i=0;i<8;i++)
function(1,buf,0,&sum);
cout<<sum/2;
return 0;
}
答案:116
注释: 对比循环可以看出,递归可以很好的提供解题思路,并缩短代码量,至于递归的缺点将在下一道题展现。
第八题
四平方和
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多4个正整数的平方和。
如果把0包括进去,就正好可以表示为4个数的平方和。
比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思)
对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对4个数排序:
0 <= a <= b <= c <= d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法
程序输入为一个正整数N (N<5000000)
要求输出4个非负整数,按从小到大排序,中间用空格分开
例如,输入:
5
则程序应该输出:
0 0 1 2
再例如,输入:
12
则程序应该输出:
0 2 2 2
再例如,输入:
773535
则程序应该输出:
1 1 267 838
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
解析:这道题我在考虑想要的满分可能需要完善题目中的每一条信息(即每个步骤),实际上是我多虑了。主键联合升序则a<=b<=c<=d,初值0000从d开始增加且总平方和每 到大于输入值时向前面数执行加1,并且回到末位满足a<=b<=c<=d的最小数,直到w等于输入值为止。
代码1:
由于我开始的错误概念的出递归代码:
#include<iostream>
#include<math.h>
#define MAX 1024
#define N 5
using namespace std;
int buf[MAX][N] = { 0 };
int i=0;
int test[N] = { 0 };
int getSum(int test[N],int j) {
int sum=0;
for (int k = 0; k < j; k++)
{
sum += test[k + 1] * test[k + 1];
}
return sum;
}
int judge(int test[N], int num) {
if (getSum(test,4) == num)
return 1;
return 0;
}
void GetBuf(int buf[][5], int* i ,int test[N], int j, int num) {
if (j == 5) {
if (judge(test, num)) {
buf[*i][1] = test[1];
buf[*i][2] = test[2];
buf[*i][3] = test[3];
buf[*i][4] = test[4];
(*i)++;
}
return;
}
for (int k = test[j-1]; k<=sqrt(num); k++) {
if (*i == 1)
return;
if (getSum(test, j - 1) > num)
break;
test[j] = k;
GetBuf(buf, i, test, j + 1, num);
}
}
int main(void) {
int anum;
cin >> anum;
GetBuf(buf, &i, test, 1, anum);
end:
cout << buf[0][1] << " " << buf[0][2] << " " << buf[0][3] << " " << buf[0][4];
}
注释:
这里开始是没有得出第一个即截至递归的。即:
if (*i == 1)return;
例如16可得 0 0 0 4和2 2 2 2要求严格的初始不需要执行此代码。但是不需要是需要添加。
虽然确实可以得到正确答案,但是当执行773535时会发现执行时间相对较长(大于3秒,更不用说比赛可能用的机器性能了),大部分时间和空间浪费在了递归结构上。
循环代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ll n;
while(cin>>n){
ll am = ceil(sqrt(n));
for(ll a = 0; a <= am; a++){
ll bm = ceil(sqrt(n - a * a));
for(ll b = a; b <= bm; b++){
ll cm = ceil(sqrt(n - a * a - b * b));
for(ll c = b; c <= cm; c++){
ll dv = n - a * a - b * b - c * c;
ll dm = sqrt(dv);
if(dm * dm == dv && dm >= c){
cout<<a<<" "<<b<<" "<<c<<" "<<dm<<endl;
goto end;
}
}
}
}
end:
n = 1;
}
return 0;
}
注释:这个代码是标准答案,循环不仅没有在多次执行上的浪费而且使用goto语句很好的解决了跳出多个循环的问题。对比递归,追求性能时循环可能成为很好的选择。
第九题
交换瓶子
有N个瓶子,编号 1 ~ N,放在架子上。
比如有5个瓶子:
2 1 3 5 4
要求每次拿起2个瓶子,交换它们的位置。
经过若干次后,使得瓶子的序号为:
1 2 3 4 5
对于这么简单的情况,显然,至少需要交换2次就可以复位。
如果瓶子更多呢?你可以通过编程来解决。
输入格式为两行:
第一行: 一个正整数N(N<10000), 表示瓶子的数目
第二行:N个正整数,用空格分开,表示瓶子目前的排列情况。
输出数据为一行一个正整数,表示至少交换多少次,才能完成排序。
例如,输入:
5
3 1 2 5 4
程序应该输出:
3
再例如,输入:
5
5 4 3 2 1
程序应该输出:
2
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
解析:这还是一道考察排序知识的问题,这里需要直到在所有简单排序里,选择排序的交换次数时O(n),注意,不是时间复杂程度,一般的段序列不需要考虑时间,而对于简 单排序交换往往伴随对比,相较而言交换次数更多。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 10240;
int N, v[maxn], pos[maxn];
int main() {
int N; scanf("%d", &N);
for(int i = 1; i <= N; ++i) {
scanf("%d", &v[i]);
pos[v[i]] = i;
}
int cnt = 0;
for(int i = 1; i <= N; ++i) {
if(v[i] == i) continue;
++cnt;
int ne = pos[i];
int t = v[i];
v[ne] = t;
v[i] = i;
pos[t] = ne;
pos[i] = i;
}
printf("%d\n", cnt);
return 0;
}
注释:这个代码是给出的标准答案,该代码有一个判环过程,但实际上是选择排序的一种变种写法。
第十题
最大比例X星球的某个大奖赛设了M级奖励。每个级别的奖金是一个正整数。
并且,相邻的两个级别间的比例是个固定值。
也就是说:所有级别的奖金数构成了一个等比数列。比如:
16,24,36,54
其等比值为:3/2
现在,我们随机调查了一些获奖者的奖金数。
请你据此推算可能的最大的等比值。
输入格式:
第一行为数字 N (0<N<100),表示接下的一行包含N个正整数
第二行N个正整数Xi(Xi<1 000 000 000 000),用空格分开。每个整数表示调查到的某人的奖金数额
要求输出:
一个形如A/B的分数,要求A、B互质。表示可能的最大比例系数
测试数据保证了输入格式正确,并且最大比例是存在的。
例如,输入:
3
1250 200 32
程序应该输出:
25/4
再例如,输入:
4
3125 32 32 200
程序应该输出:
5/2
再例如,输入:
3
549755813888 524288 2
程序应该输出:
4/1
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
解析:作为压轴大题,可以看出题目的重点不再语言技巧,更偏重编程式的解题思路和数学逻辑能力。
这是一个等比数列问题,答案要满足2个要求:当有数列 1 2 4 8 16 32 64...公比2时,公比可由两个数相除得出。这时随机取数,注意随机!
- 选出 1 4 16 64 时可以有公比 2 和 4 这时选出最大的即4 则意味数列就是 1 4 16 32...公比4。
- 选出 1 2 8 16 时可能有 2 和 4 这时应该选择2,确保每个选出的数都是合理的。
#include<iostream>
#include<math.h>
using namespace std;
double* number;
double* num;
int n;
int m;
int booljudge = 1;
double Min;
double first;
double a, b;
int getMinNo1(double* num, int l) {
int j = -1;
for (int i = 0; i < l; i++)
if (num[i] != 1) {
j = i;
break;
}
for (int i = 0; i < l; i++)
if (num[i] <num[j] && num[i] != 1) {
j = i;
}
return j;
}
int getMin(double* num, int l) {
int j = 0;
for (int i = 0; i < l; i++)
if (num[i] < num[j])
j = i;
return j;
}
void sort(double* num, int l) {
double temp;
int j;
for (int i = 0; i < l - 1; i++) {
j = getMin(num, l - i);
temp = num[l - 1 - i];
num[l - 1 - i] = num[j];
num[j] = temp;
}
}
void getMmin(double* num, int l) {
for (int i = 0; i < l - 1; i++) {
num[i] = num[i] / num[i + 1];
}
//for (int i = 0; i < l-1; i++) {
// cout << num[i];
//}
}
int judge(double* num, int l, double* min) {
for (int i = 0; i < l; i++)
{
int j;
for (j = 1; pow(*min, j) < num[i]; j++);
if (pow(*min, j) != num[i])
return 1;
}
if (*min >= 100) {
double temp = *min;
for (int i = 2; temp>100; i++)
temp = pow(*min, 1.0 / i);
*min = temp;
//cout << *min << " ";
}
return 0;
}
int main() {
cin >> n;
num = new double[n];
number = new double[n];
for (int i = 0; i < n; i++) {
cin >> num[i];
number[i] = num[i];
}
m = n;
sort(number, n);
getMmin(number, n);
first = number[getMinNo1(number, n - 1)];
//for (int i = 0; i < n - 1; i++)
//{
// cout << number[i] << " ";
//}
//cout << "\n";
//cout << first << "\n";
while (booljudge) {
sort(num, m);
getMmin(num, m);
Min = num[getMinNo1(num, m - 1)];
/*for (int i = 0; i < m - 1; i++)
{
cout << num[i] << " ";
}
cout << "\n";*/
booljudge = judge(num, m - 1, &Min);
m -= 1;
}
if (Min != first)
{
Min = Min > first ? Min / first : first / Min;//最后得出的数与第一次得出最小的数比较 不相等就相处取大的
}
b = modf(Min, &a);
if (b == 0)
b = 1;
else {
b = 1 / b;
a = a*b + 1;
}
cout << a << "/" << b;
}
注释:
至此这道题就完美的解决了,大于100时的公比根号运算我没有进行格式化输出,由1 128 16384得出36.0644/3.18767=11.3137182此数平方为128.00022近似128可见代码 正确。这道题限制条件很多,很细,对比网上许多答案都不是很完美,有的个别结果不符合,官方微信号的答案也存在循环过大幂运算过多导致的运算时间过长问题,这 里又体现出了递归解决问题大而划小的优点。!!!但是,在考场上只有4个小时的时间,显然不能把问题划的太细,尽快解决主要问题得出通用答案,才是上策,至于 这样完美的答案,就需要平时多加思索熟练掌握要点,才能尽快得出完美答案。