之前对算法一直是敬畏的,觉得很难去学习,但是通过蓝桥杯竞赛也算是强迫自己认真学习了一个多月的算法,发现算法也是可以学的。
前天竞赛就结束了,一直拖到今天才来写一篇总结,其实这次竞赛收货真的蛮大的,自己以前一直不够重视内功的培养,现在能有这么一个机会来修炼内容还是挺开心的。
感觉这次考试题和前两届去比确实难度有所增加,第九题缓存没有写好,第十题压根就没来的及做...其实第十题下来想想是能做的,只是考前最短路径这种动态规划题做的不多,所以在比赛场上就有点怯了...虽然是很想得一等奖参加决赛的,不过照这局势看难了...
今年的算法题(由于答案没有出,我的也不一定正确,以后会进行修改):
一、
奖券数目
有些人很迷信数字,比如带“4”的数字,认为和“死”谐音,就觉得不吉利。
虽然这些说法纯属无稽之谈,但有时还要迎合大众的需求。某抽奖活动的奖券号码是5位数(10000-99999),要求其中不要出现带“4”的号码,主办单位请你计算一下,如果任何两张奖券不重号,最多可发出奖券多少张。
请提交该数字(一个整数),不要写任何多余的内容或说明性文字。
思路:
前几题应该都是送分的,没什么特别需要说的,这题可以用全排列去做,比赛的时候我用了最直接的方法,10000~99999五位数字五个for循环去判断就行了,让每一位数都不为4得到的总数就是答案。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 /* run this program using the console pauser or add your own getch, system("pause") or input loop */ 5 6 int main(int argc, char *argv[]) { 7 int i,j,k,l,m; 8 int count=0; 9 for(i = 1 ; i <= 9;i++) 10 { 11 if(i != 4) 12 for(j = 0 ; j <=9 ; j++) 13 { 14 if(j != 4) 15 { 16 for(k = 0 ; k <= 9 ;k++) 17 { 18 if(k != 4) 19 { 20 for(l = 0 ; l <= 9 ;l++) 21 { 22 if(l != 4) 23 { 24 for(m = 0 ; m <= 9 ;m++) 25 { 26 if(m!=4) 27 { 28 printf("%d ",10000*i + 1000*j + 100*k + 10*l +m); 29 count++; 30 } 31 } 32 } 33 } 34 } 35 } 36 } 37 } 38 } 39 printf("%d",count); 40 return 0; 41 }
二、
星系炸弹
在X星系的广袤空间中漂浮着许多X星人造“炸弹”,用来作为宇宙中的路标。
每个炸弹都可以设定多少天之后爆炸。
比如:阿尔法炸弹2015年1月1日放置,定时为15天,则它在2015年1月16日爆炸。
有一个贝塔炸弹,2014年11月9日放置,定时为1000天,请你计算它爆炸的准确日期。
请填写该日期,格式为 yyyy-mm-dd 即4位年份2位月份2位日期。比如:2015-02-19
请严格按照格式书写。不能出现其它文字或符号。
思路:
求日期类的问题也是常见问题,解这种题我的思路是把要求的分为三部分,第一部分是题目给的当前年份的所剩余的天数,第二部分是两个年份中差的年数,第三部分是目标年所超过的天数。就这一题来说,间隔是1000天,第一部分的时间是52天,第二部分是正年数所占有的天数,第三部分就是目标所占天数。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int check(int year) 5 { 6 if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0) 7 return 1; 8 return 0; 9 } 10 11 int main(int argc, char *argv[]) { 12 int year = 2014,month = 12,day = 31,dis = 1000 - 31 - (30-9),temp; 13 int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; 14 while(dis > 0) 15 { 16 year+=1; 17 if(check(year)) 18 if(dis >= 366) 19 dis-= 366; 20 else 21 { 22 while(dis > 0) 23 { 24 temp = month-1; 25 if(temp+1 == 12) 26 temp = 0; 27 if(dis > months[temp]) 28 { 29 month = temp+1; 30 dis -= months[temp]; 31 if(temp == 1 && check(year)) 32 dis -= 1; 33 } 34 else 35 { 36 day = dis; 37 dis = 0; 38 } 39 } 40 } 41 else 42 if(dis >= 365) 43 dis-=365; 44 else 45 { 46 while(dis > 0) 47 { 48 temp = month; 49 if(temp+1 == 13) 50 temp = 0; 51 if(dis > months[temp]) 52 { 53 month = temp+1; 54 dis -= months[temp]; 55 if(temp == 1 && check(year)) 56 dis -= 1; 57 } 58 else 59 { 60 day = dis; 61 dis = 0; 62 } 63 } 64 } 65 } 66 printf("%d-%d-%d",year,month,day); 67 return 0; 68 }
三、
三羊献瑞
观察下面的加法算式:
祥 瑞 生 辉
+ 三 羊 献 瑞
-------------------
三 羊 生 瑞 气
(如果有对齐问题,可以参看【图1.jpg】)
其中,相同的汉字代表相同的数字,不同的汉字代表不同的数字。
请你填写“三羊献瑞”所代表的4位数字(答案唯一),不要填写任何多余内容。
思路:
这题是一个典型的全排列问题,直接用深度优先遍历(DFS)就可以了,给每一个数字一个编号,一共有8个不同数字,所以就是0~9的8位全排列,注意”祥“和”三“的位置不能为0就行了。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<math.h> 4 #include<string.h> 5 6 void swap(int *a, int *b) 7 { 8 int temp; 9 temp = *a; 10 *a = *b; 11 *b = temp; 12 } 13 14 void f(int array[10],int deep) 15 { 16 int i,n1,n2,n3; 17 if(deep == 8) 18 { 19 n1 = array[0] * 1000 + array[1] * 100 + array[2] * 10 + array[3]; 20 n2 = array[4] * 1000 + array[5] * 100 + array[6] * 10 + array[1]; 21 n3 = array[4] * 10000 + array[5] * 1000 + array[2] * 100 + array[1] * 10 + array[7]; 22 if(n1 + n2 == n3) 23 { 24 printf("%d + %d = %d ",n1,n2,n3); 25 } 26 } 27 for(i = deep ; i < 10 ;i++) 28 { 29 swap(&array[deep],&array[i]); 30 //编号后0,4位不能为0 31 if((deep == 0 || deep == 4)&&array[deep] == 0) 32 { 33 swap(&array[deep],&array[i]); 34 continue; 35 } 36 f(array,deep+1); 37 swap(&array[deep],&array[i]); 38 } 39 } 40 41 int main() 42 { 43 int array[10] = {1,2,3,4,5,6,7,8,9,0}; 44 f(array,0); 45 return 0; 46 }
四、
格子中输出
StringInGrid函数会在一个指定大小的格子中打印指定的字符串。
要求字符串在水平、垂直两个方向上都居中。
如果字符串太长,就截断。
如果不能恰好居中,可以稍稍偏左或者偏上一点。
下面的程序实现这个逻辑,请填写划线部分缺少的代码。
#include <stdio.h> #include <string.h> void StringInGrid(int width, int height, const char* s) { int i,k; char buf[1000]; strcpy(buf, s); if(strlen(s)>width-2) buf[width-2]=0; printf("+"); for(i=0;i<width-2;i++) printf("-"); printf("+ "); for(k=1; k<(height-1)/2;k++){ printf("|"); for(i=0;i<width-2;i++) printf(" "); printf("| "); } printf("|"); printf("%*s%s%*s",_____________________________________________); //填空 printf("| "); for(k=(height-1)/2+1; k<height-1; k++){ printf("|"); for(i=0;i<width-2;i++) printf(" "); printf("| "); } printf("+"); for(i=0;i<width-2;i++) printf("-"); printf("+ "); } int main() { StringInGrid(20,6,"abcd1234"); return 0; }
对于题目中数据,应该输出:
+------------------+
| |
| abcd1234 |
| |
| |
+------------------+
(如果出现对齐问题,参看【图1.jpg】)
注意:只填写缺少的内容,不要书写任何题面已有代码或说明性文字。
思路:
这一题只需要注意观察,因为目标字符串左右各有5个空格,又因为输出格式为%*s%s%*s,所以很明显只需要填写&" ",buf,&" "就可以了。
五、
九数组分数
1,2,3...9 这九个数字组成一个分数,其值恰好为1/3,如何组法?
下面的程序实现了该功能,请填写划线部分缺失的代码。
#include <stdio.h> void test(int x[]) { int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3]; int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8]; if(a*3==b) printf("%d / %d ", a, b); } void f(int x[], int k) { int i,t; if(k>=9){ test(x); return; } for(i=k; i<9; i++){ {t=x[k]; x[k]=x[i]; x[i]=t;} f(x,k+1); _____________________________________________ // 填空处 } } int main() { int x[] = {1,2,3,4,5,6,7,8,9}; f(x,0); return 0; }
注意:只填写缺少的内容,不要书写任何题面已有代码或说明性文字。
思路:
又是一道典型的全排列问题,没有什么好说的,在for循环中是试探着递归的,所以当回朔后要把试探结果复原,所以只需要把上面那行代码复制下来即可
{t=x[k]; x[k]=x[i]; x[i]=t;}
六、
加法变乘法
我们都知道:1+2+3+ ... + 49 = 1225
现在要求你把其中两个不相邻的加号变成乘号,使得结果为2015
比如:
1+2+3+...+10*11+12+...+27*28+29+...+49 = 2015
就是符合要求的答案。
请你寻找另外一个可能的答案,并把位置靠前的那个乘号左边的数字提交(对于示例,就是提交10)。
注意:需要你提交的是一个整数,不要填写任何多余的内容。
思路:
这题直接暴力解了,先用一个循环来找第一个*位置,第二个循环来找下一个*位置,最后一个循环来计算结果就行了。
#include<stdio.h> int main() { int i,j,k,result = 0; //第一个乘号只能出现在前1~47个数字后面 for(i = 1;i<=47;i++) { //第二个乘号只能出现在i+1~48个数字后面 for(j = i+1 ; j<= 48;j++) { result = 0; for(k = 1 ; k <= 49 ;k++) { if(i == k && j == i+1) { result = result + (k*(k+1)*(k+2)); k+=2; continue; } if(i == k || j == k) { result = result + (k*(k+1)); k++; continue; } else { result += k; } } if(result == 2015) { printf("%d ",i); } } } return 0; }
七、
牌型种数
小明被劫持到X赌城,被迫与其他3人玩牌。
一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张。
这时,小明脑子里突然冒出一个问题:
如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序,自己手里能拿到的初始牌型组合一共有多少种呢?
请填写该整数,不要填写任何多余的内容或说明文字。
思路:
这题其实也没什么说的,还是深度优先遍历(DFS)就行了,对于每种点数的牌取法有这些可能性:不取、取一张、取两张、取三张、取四张,我写的可能不太好,还加上了缓存。
1 #include <stdio.h> 2 3 long array[13][13]; 4 5 long long f(int n,int deep) 6 { 7 long long i,count1 = 0,count2 = 0,count3 = 0,count4 = 0,count5 = 0; 8 if(n >= 13 && deep <=13) 9 { 10 if(n == 13) 11 return 1; 12 else 13 return 0; 14 } 15 if(deep > 13) 16 return 0; 17 if(array[n][deep]) 18 return array[n][deep]; 19 for(i = 1 ; i <= 13 ;i++) 20 { 21 count1=f(n,deep+1); 22 if(array[n][deep+1] == 0) 23 array[n][deep+1] = count1; 24 count2=f(n+1,deep+1); 25 if(array[n+1][deep+1] == 0) 26 array[n+1][deep+1] = count2; 27 count3=f(n+2,deep+1); 28 if(array[n+2][deep+1] == 0) 29 array[n+2][deep+1] = count3; 30 count4=f(n+3,deep+1); 31 if(array[n+3][deep+1] == 0) 32 array[n+3][deep+1] = count4; 33 count5=f(n+4,deep+1); 34 if(array[n+4][deep+1] == 0) 35 array[n+4][deep+1] = count5; 36 } 37 return count1+count2+count3+count4+count5; 38 } 39 40 int main() 41 { 42 int n,i,j; 43 for(i = 0 ; i < 14 ;i++) 44 { 45 for(j = 0 ; j < 14 ;j++) 46 { 47 array[i][j] = 0; 48 } 49 } 50 printf("%I64d",f(0,0)); 51 52 return 0; 53 }
八、
移动距离
X星球居民小区的楼房全是一样的,并且按矩阵样式排列。其楼房的编号为1,2,3...
当排满一行时,从下一行相邻的楼往反方向排号。
比如:当小区排号宽度为6时,开始情形如下:
1 2 3 4 5 6
12 11 10 9 8 7
13 14 15 .....
我们的问题是:已知了两个楼号m和n,需要求出它们之间的最短移动距离(不能斜线方向移动)
输入为3个整数w m n,空格分开,都在1到10000范围内
w为排号宽度,m,n为待计算的楼号。
要求输出一个整数,表示m n 两楼间最短移动距离。
例如:
用户输入:
6 8 2
则,程序应该输出:
4
再例如:
用户输入:
4 7 20
则,程序应该输出:
5
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
思路:
这一题相当的简单,也就在初始化数组内容的时候会有点绕,两个居民居中的距离就等于,abs(x1-x2)+abs(y1-y2)。
数据规模:
这题数据规模其实也不大,我们只需要创建个10000X10000的2维数组即可。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<math.h> 4 #include<string.h> 5 6 int array[10000][10000]; 7 8 int main() 9 { 10 int i,j,w,m,n,max,temp = 1,locx1,locx2,locy1,locy2; 11 scanf("%d %d %d",&w,&m,&n); 12 max = m>n?m:n; 13 for(i = 0 ; i <= max/w ;i++) 14 { 15 for(j = 0 ; j < w ;j++) 16 { 17 if(i % 2== 0) 18 { 19 array[i][j] = temp; 20 if(temp == m || temp == n) 21 { 22 locx1 = i; 23 locy1 = j; 24 } 25 temp++; 26 } 27 else 28 { 29 array[i][w-j-1] = temp; 30 if(temp == m || temp == n) 31 { 32 locx2 = i; 33 locy2 = w-j-1; 34 } 35 temp++; 36 } 37 } 38 } 39 printf("%d",abs(locx1-locx2) + abs(locy1-locy2)); 40 return 0; 41 }
九、
垒骰子
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 10^9 + 7 的结果。
不要小看了 atm 的骰子数量哦~
「输入格式」
第一行两个整数 n m
n表示骰子数目
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。
「输出格式」
一行一个数,表示答案模 10^9 + 7 的结果。
「样例输入」
2 1
1 2
「样例输出」
544
「数据范围」
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
思路:
这题稍微难一些,而且测试数据规模非常大,所以要做相应的处理,首先我们考虑题上给的两个筛子的情况,题上给的测试数据中不能相互碰到的筛子是1,2两个面,所以我们可以这样想:首先第一个筛子面对我们的数字应该是有6中情况,但是筛子还可以旋转同样可以使该数字对着我们。所以我们应该找两个面来确定筛子的一种情况,我们来用上面和前面两个面来看。前面有6种选择,确定前面后上面有4种选择,所以一个筛子的状态就有24种选择,那么两个就有24X24=576种,还需要减去1上2下或2上1下两种情况。1为上有4种情况2为下有4种情况,所以算上2上1下一共应该有4x4x2=32种情况,结果就应该只有576-32=544种情况。
数据规模:
这一题我用暴力DFS到8个筛子好像就不行了,非常的大,所以需要保存每一步的过程。但是考试的时候也可能是太紧张,或者还是不熟练吧,这一点没有处理好所以估计这题得不了几分了...请高手多指点...
#include<stdio.h> #include<math.h> //做缓存的数组 int array[100000000] = {0}; int way[24][2] = { {1,2},{1,3},{1,5},{1,6}, {2,1},{2,3},{2,4},{2,6}, {3,1},{3,2},{3,4},{3,5}, {4,2},{4,2},{4,3},{4,6}, {5,1},{5,3},{5,4},{5,6}, {6,1},{6,2},{6,4},{6,5} }; int checkWay(int n,int up,int m,int check[36][2]) { int i; int down; down = way[n][1]>3?way[n][1]-3:way[n][1]+3; for(i = 0 ; i < m ; i++) { if(check[i][0] == up && check[i][1] == down) return 0; if(check[i][1] == up && check[i][0] == down) return 0; } return 1; } long long f(int n,int up,int m,int check[36][2]) { int i; long long count = 0; if(n == 0) { return 1; } for(i = 0 ; i < 24 ;i++) { if(up == 0) { count += f(n-1,way[i][1],m,check); } else { if(checkWay(i,up,m,check)) { count += f(n-1,way[i][1],m,check); } else continue; } } return count; } int main() { int n,m; int check[36][2]; int i,j; scanf("%d %d",&n,&m); for(i = 0; i < m ;i++) { scanf("%d %d",&check[i][0],&check[i][1]); } printf("%I64d",f(n,0,m,check)); return 0; }
十、
生命之树
在X森林里,上帝创建了生命之树。
他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值。
上帝要在这棵树内选出一个非空节点集S,使得对于S中的任意两个点a,b,都存在一个点列 {a, v1, v2, ..., vk, b} 使得这个点列中的每个点都是S里面的元素,且序列中相邻两个点间有一条边相连。
在这个前提下,上帝要使得S中的点所对应的整数的和尽量大。
这个最大的和就是上帝给生命之树的评分。
经过atm的努力,他已经知道了上帝给每棵树上每个节点上的整数。但是由于 atm 不擅长计算,他不知道怎样有效的求评分。他需要你为他写一个程序来计算一棵树的分数。
「输入格式」
第一行一个整数 n 表示这棵树有 n 个节点。
第二行 n 个整数,依次表示每个节点的评分。
接下来 n-1 行,每行 2 个整数 u, v,表示存在一条 u 到 v 的边。由于这是一棵树,所以是不存在环的。
「输出格式」
输出一行一个数,表示上帝给这棵树的分数。
「样例输入」
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5
「样例输出」
8
「数据范围」
对于 30% 的数据,n <= 10
对于 100% 的数据,0 < n <= 10^5, 每个节点的评分的绝对值不超过 10^6 。
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
思路:
应该就是一个迪杰斯特拉算法,这题把它看成一个求连通图最长路径会好想的多。
数据规模:
考场上没有做,数据规模也没有认真分析。
这题没做就先不贴代码了,明天尽量把它做了然后补上。
总结:
这次比赛说实话还是平时关注这方面的太少了,所以遇到一些不寻常的题就会卡在那里,第九题做完其实还有1个小时,但是缓存一直想不明白哪里出了问题...所以就在哪里死扣了,第十题知道往最短路径上面想,但是因为就前一天刚看的最短路径是思路不是很有信心能写出来,所以一上来就有点想放弃了...以后一定要改正这种思想啊...不过最重要的还是平时应该多加强写基础的学习...