第八届蓝桥杯(2017)-省赛题解_C/C++__大学A组

感悟总结

第一道填空题和第二道填空题一上来就dfs和bfs,确实有压力。
第三题魔方状态又特别复杂,第四题,方格分割,如果没有想到从中心点两边深搜,那估计这年考试的兄弟心态都炸了。
第六题动态规划,第七题扩展欧几里得,好在后面的编程大题难度不是太高,最后两道大题纯暴力也能得不少分数!
个人感觉这年的考试题是真题里面难度最大的一年(也可能是我水平太低了)但实际上前面难,后面难度会降低一点。

本文有自己的思路,也有对网络上思路的借鉴,但不保证百分百正确,如有错误不足,希望得到您的指正!

一、迷宫(填空题)

X星球的一处迷宫游乐场建在某个小山坡上。
它是由10x10相互连通的小房间组成的。
房间的地板上写着一个很大的字母。我们假设玩家是面朝上坡的方向站立,则:L表示走到左边的房间,R表示走到右边的房间,U表示走到上坡方向的房间,D表示走到下坡方向的房间。
X星球的居民有点懒,不愿意费力思考。他们更喜欢玩运气类的游戏。这个游戏也是如此!开始的时候,直升机把100名玩家放入一个个小房间内。
玩家一定要按照地上的字母移动。
迷宫地图如下:

UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR

请你计算一下,最后,有多少玩家会走出迷宫?
而不是在里边兜圈子。
请提交该整数,表示走出迷宫的玩家数目,不要填写任何多余的内容。
如果你还没明白游戏规则,可以参看一个简化的4x4迷宫的解说图
在这里插入图片描述

  1. 就考试来说我更推荐手画,这道题手算一共用时不超过6,7分钟,编程去做的时间远远大于手算时间,技巧就是先把四边画好,将可以通过的地方画黑,不能通过的画叉,一个点如果能走出去,那么所有能到达这个点的点都能走出去。不在四边时,L在左R在右直接画叉,D在上U在下直接画叉,最后数涂黑的地方一共有31个。

  2. 就编程而言,这道题是明显的暴力深搜+标记

#include<bits/stdc++.h>
using namespace std;
int vis[15][15];//用来标记是否走出去 
int str[15][15];//用来储存迷宫 
int ans;//用来计数走出去的个数 

bool check(int x,int y){
    return x>=0&&x<10&&y>=0&&y<10; //判断是否走出去,没有走出去返回false,走出去返回true
}
void dfs(int x,int y)
{ 
    if(!check(x,y))
    {
        ans++;
        return; 
    }
    if(vis[x][y]==0)
    {
        vis[x][y]=1;//表示这个位置已经走过,加标记
        switch(str[x][y])
        {
            case 'U':return dfs(x-1,y);
            case 'D':return dfs(x+1,y);
            case 'L':return dfs(x,y-1);
            case 'R':return  dfs(x,y+1);
        }
    }
}

int main(){
    int q=0;
    string s="UDDLUULRULUURLLLRRRURRUURLDLRDRUDDDDUUUUURUDLLRRUUDURLRLDLRLULLURLLRDURDLULLRDDDUUDDUDUDLLULRDLUURRR";
    for(int i=0;i<10;i++){
        for(int j=0;j<10;j++)
        {
            str[i][j]=s[q];
            q++;
        }
    } 
    for(int i=0;i<10;i++){   
        for(int j=0;j<10;j++){
            memset(vis,0,sizeof(vis));//每次循环都要将标记数组重新清零
            dfs(i,j);
        }
    }
    cout<<ans<<endl;
    return 0;
}
二、跳蚱蜢(填空题)

有9只盘子,排成1个圆圈。其中8只盘子内装着8只蚱蜢,有一个是空盘。我们把这些蚱蜢顺时针编号为 1~8每只蚱蜢都可以跳到相邻的空盘中,也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。
请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,并且保持空盘的位置不变(也就是1-8换位,2-7换位,…),至少要经过多少次跳跃?
注意:要求提交的是一个整数,请不要填写任何多余内容或说明文字。
在这里插入图片描述

思路:

  1. 怎么去表示这个圆圈?用数组,我们将空盘记为0,初始状态为012345678,目标状态为087654321。
  2. 怎么进行状态的演变,第一轮开始,1和8都可以跳到0位置,7和2也可以跳到空盘里。因此每一轮都可以产生四个新的状态。我们可以定义一个数组包含1,2,-1,-2四个数来表示四个方位,但是会超出数组边界,就用到(x+9)%9,用当前位置+9再对9取模,就是空盘交换的位置。
  3. 求最少的步数,明显要考虑广搜。实际上这道题的原理并不复杂,只是考查的内容较多,有结构体的声明,set去重和队列的应用。
#include<bits/stdc++.h>
using namespace std;

char *start="012345678";//声明两个字符类型的指针,表示初始状态和目标状态 
char *target="087654321";

struct StateAndLevel{//声明结构体,状态和层次 
    char *state;//当前状态,即对应此时的字符串 
    int level; //层次 ,即走了几步 
    int pos0;//0的初始位置 
    StateAndLevel(char* state,int level,int pos0):state(state),level(level),pos0(pos0){}//构造函数 
}; 

struct cmp{//声明结构体,用于和set进行比较 
    bool operator()(char *a,char *b){
        return strcmp(a,b)<0;//表示比较结果不相等 
    }
};

//声明队列和set集合 
queue<StateAndLevel>q;
set<char*,cmp>allState;//cmp是加的比较 

//交换的函数 
void swap(char *s,int a,int b){
    char t=s[a];
    s[a]=s[b];
    s[b]=t;
}

void addNei(char* state,int pos,int newPos,int le){//添加邻居 
    char *new_state=(char *)malloc(9*sizeof(char));//为新的状态分配内存空间 
    strcpy(new_state,state);//将state拷贝到new-state中 
    swap(new_state,pos,newPos);//交换
    if(allState.find(new_state)==allState.end()){//set内找不到重复元素就执行 
        allState.insert(new_state);//把新的状态添加到set集合中 
        q.push(StateAndLevel(new_state,le+1,newPos));//将新的状态放在队列的末端 
    }
} 

int main(){
    q.push(StateAndLevel(start,0,0));//初始状态,层次为0,放到队列中 
    while(!q.empty()){//如果队列不为空,就执行循环 
        StateAndLevel sal=q.front();//访问队列头结点 
        char *state=sal.state;//声明状态 
        int le=sal.level;//声明层次 
        int pos0=sal.pos0;//声明0的位置 
        allState.insert(state); //添加到set避免重复

        if(strcmp(state,target)==0){//退出循环的出口,状态和目标状态相同执行 
            printf("%d",le);
            return 0;
        }

        int new_pos=(pos0-1+9)%9;//空盘与右边第一个盘子交换 
        addNei(state,pos0,new_pos,le); 
        new_pos=(pos0+1+9)%9;//空盘与左第一个盘子交换 
        addNei(state,pos0,new_pos,le); 
        new_pos=(pos0-2+9)%9;//空盘右二交换 
        addNei(state,pos0,new_pos,le);
        new_pos=(pos0+2+9)%9;//空盘左二交换 
        addNei(state,pos0,new_pos,le);  
        q.pop();//弹出队列第一个元素 
    } 
    return 0;
} 
三、魔方状态(填空题)

二阶魔方就是只有2层的魔方,只由8个小块组成。如下所示:
在这里插入图片描述
小明很淘气,他只喜欢3种颜色,所有把家里的二阶魔方重新涂了颜色,如下:
前面:橙色
右面:绿色
上面:黄色
左面:绿色
下面:橙色
后面:黄色
  请你计算一下,这样的魔方被打乱后,一共有多少种不同的状态。
  如果两个状态经过魔方的整体旋转后,各个面的颜色都一致,则认为是同一状态。
  请提交表示状态数的整数,不要填写任何多余内容或说明文字。
  
  
过一段时间会补上

在这里插入代码片
四、方格分割(填空题)

6x6的方格,沿着格子的边线剪开成两部分,要求这两部分的形状完全相同。如图:p1.png, p2.png, p3.png就是可行的分割法。
试计算:包括这2种分法在内,一共有多少种不同的分割方法。

注意:旋转对称的属于同一种分割法。请提交该整数,不要填写任何多余的内容或说明文字。
  在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

技巧就是从中心对称点开始深搜,同时向两边深搜

#include<bits/stdc++.h>
using namespace std;
int ans;
int dire[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int vis[7][7];
void dfs(int x,int y){
    //递归出口 
    if(x==0||y==0||x==6||y==6){
        ans++;
        return;
    }

    //当前的点标注为已访问
    vis[x][y]=1;
    //对称点也标注为已访问
    vis[6-x][6-y]=1;

    for(int k=0;k<4;k++){
        int xx=x+dire[k][0];
        int yy=y+dire[k][1];

        if(xx<0||xx>6||yy<0||yy>6)
            continue;
        if(!vis[xx][yy]){
            dfs(xx,yy);
        }
    } 
    vis[x][y]=0;
    vis[6-x][6-y]=0;
}
int main(){
    dfs(3,3);
    cout<<ans/4<<endl;
    return 0;
}//509
五、字母组串(代码补充)

由 A,B,C 这3个字母就可以组成许多串。比如:“A”,“AB”,“ABC”,“ABA”,“AACBB” …
  现在,小明正在思考一个问题:如果每个字母的个数有限定,能组成多少个已知长度的串呢?
  他请好朋友来帮忙,很快得到了代码,解决方案超级简单,然而最重要的部分却语焉不详。请仔细分析源码,填写划线部分缺少的内容。
  对于下面的测试数据,小明口算的结果应该是:6,19。

#include<bits/stdc++.h>
using namespace std;
// a个A,b个B,c个C 字母,能组成多少个不同的长度为n的串。
int f(int a, int b, int c, int n)
{
    if(a<0 || b<0 || c<0) return 0;
    if(n==0) return 1; 

    return f(a-1,b,c,n-1)+f(a,b-1,c,n-1)+f(a,b,c-1,n-1);  // 代码补充
}
int main()
{
    printf("%d\n", f(1,1,1,2));
    printf("%d\n", f(1,2,3,3));
    return 0;
}

由于递归的出口判断条件是a小于0,或者b小于0,或者c小于0,或者n等于0,那么在填空处,a,b,c,n都在减少,代码的思路就是每抽出一个字母,串的长度减去1,直到抽出与n相等的为止,返回1代表一种可能。
填空处的答案为:f(a-1,b,c,n-1)+f(a,b-1,c,n-1)+f(a,b,c-1,n-1);

六、最大公共子串(代码补充)

最大公共子串长度问题就是:求两个串的所有子串中能够匹配上的最大长度是多少。
  比如:“abcdkkk” 和 “baabcdadabc”,可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。
  下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。
  请分析该解法的思路,并补全划线部分缺失的代码。

#include <stdio.h>
#include<string.h>
#define N 256
int f(const char *s1,const char* s2){
    int a[N][N];
    int len1=strlen(s1);
    int len2=strlen(s2);
    int i,j;
    memset(a,0,sizeof(int)*N*N);
    int max=0;
    for(i=1;i<=len1;i++){
        for(j=1; j<=len2; j++){
            if(s1[i-1]==s2[j-1]) {
                a[i][j] = ___a[i-1][j-1]+1__; //代码补充
                if(a[i][j] > max) max = a[i][j];
            }
        }
    }
    return max;
}
int main(){
    printf("%d\n",ff("abcdkkk", "acbdchd"));
    return 0;
}
七、正则问题

考虑一种简单的正则表达式:只由 x ( ) | 组成的正则表达式。
小明想求出这个正则表达式能接受的最长字符串的长度。
  例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是: xxxxxx,长度是6。
  输入:一个由x()|组成的正则表达式。输入长度不超过100,保证合法。
  输出:这个正则表达式能接受的最长字符串的长度。
  例如,输入:((xx|xxx)x|(x|xx))xx ,程序应该输出:6

#include<bits/stdc++.h>
using namespace std;
string str;
int pos,len;
int f(){
    int num,tmp;
    while(pos<len){
        if(str[pos]=='('){//如果我们遇到(就进入一个新的函数,并用新的返回值计算 
            pos++;
            num+=f();//等待后面的结果,并累加到num中 
        }
        else if(str[pos]=='x'){//表示遇到的是‘x ’ 
            pos++;
            num++;
        }
        else if(str[pos]=='|'){
            pos++;
            tmp=max(num,tmp);
            num=0;
        }
        else if(str[pos]==')'){//如果遇到)就退出当前函数 
            pos++; 
            break;
        }
    }
    tmp=max(num,tmp);//保证最后一段也有结果 
    return tmp;
}
int main(){
    cin>>str;
    len=str.length();
    int ans;
    ans=f();
    cout<<ans<<endl;
    return 0;
}
八、包子凑数

小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入:第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
输出:一个整数代表答案。如果凑不出的数目有无限多个,输出INF。
例如,输入:
2
4
5
程序应该输出:
6
再例如,输入:
2
4
6
程序应该输出:
INF

样例解释:
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。
对于样例2,所有奇数都凑不出来,所以有无限多个。

实际上这就是对2013年第八道题买不到的数目的拓展。
我们已经知道买不到的情况就是输入的种类数目不是互质数(最大公约数不为1)。通过两层for循环,求最大公约数的模板求出所有的数是不是两两互质的,如果最大公约数不是1,则输出INF!否则通过完全背包的思路

#include<bits/stdc++.h>
using namespace std;
bool f[10000];

int gcd(int a,int b)
{return b==0?a:gcd(b,a%b);}

int main(){
    int n,judge=1,a[101],count=0;
    f[0]=true;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
            if(gcd(a[i],a[j])!=1){
                printf("INF\n");
                judge=0;
                break;
            }
        }
    }
    if(judge==1){
        for(int i=1;i<=n;i++){
            for(int j=0;j<10000;j++){
                if(f[j]){
                    f[j+a[i]]=true;//完全背包 
                }
            }
        }
        for(int i=1;i<10000;i++){
            if(f[i]==0){
                count++;
            }
        }
        printf("%d",count);}

    return 0; 
}
九、分巧克力

儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:

  1. 形状是正方形,边长是整数
  2. 大小相同

例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?

输入:第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。

输出:输出切出的正方形巧克力最大可能的边长。

样例输入:
2 10
6 5
5 6
样例输出:
2

用二分法优化暴力枚举

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,k;
    int h[100000];
    int w[100000];
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>h[i]>>w[i];
    }
    int r=100001;//右边界
    int l=1;//左边界 
    int ans=0; 
    while(l<=r)
    {
        int mid=(l+r)/2;
        int cnt=0;
        for(int i=0;i<n;i++)
            cnt+=(h[i]/mid)*(w[i]/mid);
        if(cnt>=k)
        {
            l=mid+1;//左边界加1 
            ans=mid;
        }
        else
            r=mid-1;
    }
    cout<<ans<<endl;
    return 0;
}
十、油漆面积

X星球的一批考古机器人正在一片废墟上考古。该区域的地面坚硬如石、平整如镜。管理人员为方便,建立了标准的直角坐标系。

每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。

经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。

矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。

为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。

其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。

注意,各个矩形间可能重叠。
本题的输入为若干矩形,要求输出其覆盖的总面积。
输入格式:

第一行,一个整数n,表示有多少个矩形(1<=n<10000)

接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。

(0<= x1,y1,x2,y2 <=10000)
输出格式:

一行一个整数,表示矩形覆盖的总面积。
例如,

输入:

3

1 5 10 10

3 1 20 20

2 7 15 17
程序应该输出:

340
再例如,

输入:

3

5 2 10 6

2 7 12 10

8 1 15 15
程序应该输出:

128

暴力法

#include<bits/stdc++.h>
using namespace std;
bool a[10005][10005];//用int会超出题目的范围 
void judge(int x1,int y1,int x2,int y2){
    for(int i=x1;i<x2;i++){
        for(int j=y1;j<y2;j++){
            a[i][j]=1;
        }
    }
} 
int main(){
    int n,sum;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        int x1,y1,x2,y2;
        scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
        judge(x1,y1,x2,y2);
    }
    for(int i=0;i<10005;i++){
        for(int j=0;j<10005;j++){
            if(a[i][j]==1)
                sum++;
        }
    }
    printf("%d\n",sum);
    return 0;
} 

线段树+扫描线
这个解法过段时间会补上

在这里插入代码片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值