2015第六届蓝桥杯竞赛感悟

  之前对算法一直是敬畏的,觉得很难去学习,但是通过蓝桥杯竞赛也算是强迫自己认真学习了一个多月的算法,发现算法也是可以学的。

  前天竞赛就结束了,一直拖到今天才来写一篇总结,其实这次竞赛收货真的蛮大的,自己以前一直不够重视内功的培养,现在能有这么一个机会来修炼内容还是挺开心的。

  感觉这次考试题和前两届去比确实难度有所增加,第九题缓存没有写好,第十题压根就没来的及做...其实第十题下来想想是能做的,只是考前最短路径这种动态规划题做的不多,所以在比赛场上就有点怯了...虽然是很想得一等奖参加决赛的,不过照这局势看难了...

今年的算法题(由于答案没有出,我的也不一定正确,以后会进行修改):

一、

奖券数目

有些人很迷信数字,比如带“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个小时,但是缓存一直想不明白哪里出了问题...所以就在哪里死扣了,第十题知道往最短路径上面想,但是因为就前一天刚看的最短路径是思路不是很有信心能写出来,所以一上来就有点想放弃了...以后一定要改正这种思想啊...不过最重要的还是平时应该多加强写基础的学习...

原文地址:https://www.cnblogs.com/madpanda/p/4422493.html