第六届蓝桥杯C++C组 A~F题题解

蓝桥杯历年国赛真题汇总:Here

1. 分机号

X老板脾气古怪,他们公司的电话分机号都是3位数,老板规定,所有号码必须是降序排列,且不能有重复的数位。比如:

751,520,321 都满足要求,而,
766,918,201 就不符合要求。

现在请你计算一下,按照这样的规定,一共有多少个可用的3位分机号码?

请直接提交该数字,不要填写任何多余的内容。

答案:120

利用 C++ STL排列组合函数

void solve() {
    int ans = 0;
    for (int i = 9; i >= 2; --i)
        for (int j = i - 1; j >= 1; --j)
            for (int k = j - 1; k >= 0; --k)if (i > j and j > k)ans++;
    cout << ans;
}

2. 五星填数

如【图1.png】的五星图案节点填上数字:1~12,除去7和11。
要求每条直线上数字和相等。

如图就是恰当的填法。

请你利用计算机搜索所有可能的填法有多少种。
注意:旋转或镜像后相同的算同一种填法。

请提交表示方案数目的整数,不要填写任何其它内容。

答案:12

给所有点都压入一维数组,然后重排序判断即可

void solve() {
    int a[] = {1, 2, 3, 4, 5, 6, 8, 9, 10, 12}; // Size = 10
    int ans = 0;
    do {
        int a1 = a[1] + a[2] + a[3] + a[4];
        int a2 = a[0] + a[2] + a[5] + a[8];
        int a3 = a[0] + a[3] + a[6] + a[9];
        int a4 = a[1] + a[3] + a[5] + a[7];
        int a5 = a[4] + a[6] + a[7] + a[9];
        if (a1 == a2 and a2 == a3 and a3 == a4 and a4 == a5)ans++;
    } while (next_permutation(a, a + 10));
    cout << ans;
}

或者规律解法:为了满足这个题目要求,我们以1为对称

5 2
3 4
1

3 2
5 4
1

5 4
3 2
1

5 2
3 4
1

可以看到5,3在1左边的排序有4个,再算上
5,2和5,4在左边的排序
答案:\(4 * 3 = 12\)

3.二分法

二分查找法十分常用,适用于在有序的队列中搜索。
下面的程序在有序整数数组中搜索,找不到会返回-1

#include <stdio.h>
#define N 23

int find(int* m, int k)
{
	int lo = 0;
	int hi = N-1;
	
	while(lo<=hi){
		int mid = (lo+hi)/2;
		if      (k<m[mid]) hi = mid-1;
		else if (k>m[mid]) lo = mid+1;
		else ______________________________;  //填空位置
        // 答案:return mid + 1;
	}
	
	return -1;
}

int main()
{
	int m[N] = {1,4,5,8,11,13,14,15,22,27,35,46,48,49,49,51,60,62,62,62,71,74,88};
	printf("%d ", find(m,19));
	printf("%d ", find(m,49));
	printf("%d ", find(m,22));
	printf("\n");
	return 0;
}

请分析流程,填写划线部分缺失代码。

注意:只填写缺少的代码,不要写题面已有代码或任何其它多余内容。

答案:return mid + 1;

4.机器人繁殖

X星系的机器人可以自动复制自己。它们用1年的时间可以复制出2个自己,然后就失去复制能力。
每年X星系都会选出1个新出生的机器人发往太空。也就是说,如果X星系原有机器人5个,
1年后总数是:5 + 9 = 14
2年后总数是:5 + 9 + 17 = 31

如果已经探测经过n年后的机器人总数s,你能算出最初有多少机器人吗?

数据格式:

输入一行两个数字n和s,用空格分开,含义如上。n不大于100,s位数不超过50位。

要求输出一行,一个整数,表示最初有机器人多少个。

例如:
用户输入:

2 31

则程序应该输出:

5

再例如:
用户输入:

97 2218388550399401452619230609499

则程序应该输出:

8

资源约定:
峰值内存消耗 < 512M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

【思路分析】

我们很容易发现,对于这一道题目,是有迹可循的,对于此我们可以简单的进行分析。

首先我们规定以下符号:

n:年数 s:机器人总数 k:最初的机器人数量
  1. 首先我们先看一下题目,其中我们可以发现:

    那么我们将这个公式进行推广,通过数学归纳法可以得到这样的一个公式:

    第n年所能产生的机器人个数 = \(2^{n}k-2^{n}+1\)

    这个公式不难理解,原因也很简单,每年新增人数如下:
    我们很容易发现k前的系数是\(2^{n-1}\) 而后面的偏置就是系数-1。

  2. 接下来我们在推导一下机器人总数s:

    我们知道机器人总数s不过就是每年新增人数之和,故:

    \(s = \sum_{i=1}^n(2^{i}k-2^{i}+1)+1\\ s = (2^{n+1}-1)k-(2^{n+1}-1)+n+1\)

  3. 那么当这个等式出来以后,我们就可以惊奇的发现,整个题目都变成了一个三元方程,后面我们只需要把这个方程解出来即可,唯一需要注意的可能就是关于大数除法的问题了。为了方便计算,我们把等式稍微变形一下:

    \(s-n-1 = (2^{n+1}-1)(k-1)\)

【AC代码】

using ll = long long;
void solve() {
    int n; double s; // 原本应该用大数存储,但这里double也存的下
    cin >> n >> s;
    ll k = (s - n - 1) / (pow(2, n + 1) - 1) + 1;
    cout << k;
}

5.穿越雷区

X星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。
某坦克需要从A区到B区去(A,B区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?

已知的地图是一个方阵,上面用字母标出了A,B区,其它区都标了正号或负号分别表示正负能量辐射区。
例如:

A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

坦克车只能水平或垂直方向上移动到相邻的区。

数据格式要求:

输入第一行是一个整数n,表示方阵的大小, 4<=n<100
接下来是n行,每行有n个数据,可能是A,B,+,-中的某一个,中间用空格分开。
A,B都只出现一次。

要求输出一个整数,表示坦克从A区到B区的最少移动步数。
如果没有方案,则输出-1

例如:
用户输入:

5
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

则程序应该输出:

10

资源约定:
峰值内存消耗 < 512M
CPU消耗 < 1000ms

#include<bits/stdc++.h>
using namespace std;
int Min = 99999999; //用来更新最小值
int a[101][101], book[101][101]; //book标记走过的路
int x, y, x2, y2; //(x,y)标记A的坐标、(x2,y2)标记B的坐标。
int n;//矩阵大小n*n
char s;//存储临时字符
void dfs(int x, int y, int step) {
    int next[4][2] = {{0, 1}, {1, 0}, { -1, 0}, {0, -1}}; //定义方向数组
    int tx, ty; //定义下一个要搜索的坐标
    for (int i = 0; i <= 3;
            i++) { //循环(x,y)的右下左上坐标
        tx = x + next[i][0];
        ty = y + next[i][1];
        //超出边界即跳过本次循环执行下一次
        if ( tx > n || ty > n || tx < 1 || ty < 1)
            continue;
        if (tx == x2
                && ty == y2) { //这里之所以把满足条件更新Min放在循环内是因为下面的B点没有办法被搜素到,所以用下一个将要搜索的点来判断,step+1也是因为B点没有被dfs所以要加1.
            if (step + 1 < Min)
                Min = step + 1;
            return;
        }
        int p;
        p = a[x][y];
        p = 0 - p; //这个p就是本题的重点了,(p==0 && step==0)就是特判从A点进入辐射区。然后就是如果搜索到了B点该怎么办?我们知道B点的状态是a[tx][ty]=0,B点的0肯定和辐射区的1、-1不相等,所以B点不能进入dfs。
        if ((a[tx][ty] == p && book[tx][ty] == 0) || (p == 0 && step == 0)) {
            book[tx][ty] = 1; //标记走过的点
            dfs(tx, ty, step + 1);
            book[tx][ty] = 0; //一次深搜结束后取消标记
        }
    }
    return;//回溯,如果到这个点上下左右都不能走,回到上一个点。
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            cin >> s;
            if (s == '+')
                a[i][j] = 1;
            if (s == '-')
                a[i][j] = -1;
            if (s == 'A') {
                x = i; y = j;
            }
            if (s == 'B') {
                x2 = i; y2 = j;
            }
        }
    //处理矩阵并且标记A、B坐标。
    dfs(x, y, 0); //从A的坐标开始搜索。
    if (Min == 99999999) {
        cout << -1;
    } else
        cout << Min;
    return 0;
}

6.切开字符串

Pear有一个字符串,不过他希望把它切成两段。
这是一个长度为N(<=10^5)的字符串。
Pear希望选择一个位置,把字符串不重复不遗漏地切成两段,长度分别是t和N-t(这两段都必须非空)。

Pear用如下方式评估切割的方案:
定义“正回文子串”为:长度为奇数的回文子串。
设切成的两段字符串中,前一段中有A个不相同的正回文子串,后一段中有B个不相同的非正回文子串,则该方案的得分为A*B。

注意,后一段中的B表示的是:“...非正回文...”,而不是: “...正回文...”。
那么所有的切割方案中,A*B的最大值是多少呢?

【输入数据】
输入第一行一个正整数N(<= \(10^5\)
接下来一行一个字符串,长度为N。该字符串仅包含小写英文字母。
【输出数据】
一行一个正整数,表示所求的A*B的最大值。
【样例输入】

10
bbaaabcaba

【样例输出】

38

【数据范围】
对于20%的数据,N<=100
对于40%的数据,N<=1000
对于100%的数据,N<=10^5

资源约定:
峰值内存消耗 < 512M
CPU消耗 < 1000ms

解题思路:
1.先枚举前后串的分割情况,每枚举出一种情况就分别对前串和后串进行处理
2.处理过程:计算前串的子串正回文个数,计算后串的子串不是正回文的个数
(注意:后串的子串只要不符合正回文都算一种情况,即它可以是非回文,可以是偶数长度回文。处理的时候我们只需要用一个check函数检测是不是正回文,前串统计返回值是1的情况,后串统计返回值是0的情况)

一开始我被误导了,以为后串的“非正回文子串”是偶数长度的回文子串

3.每处理完一种分割情况就尝试能否更新最大值


注意事项:题目要求计算的是不相同的子串,可以用一个map来查重

int n, A, B, max_ans = -1;
string s, a, b;
int check(string
          str) {  //用于检测字符串是否是正回文子串
    if (str.length() % 2 == 0) { //如果长度是奇数,直接判错
        return 0;
    }
    for (int i = 0; i < str.length() / 2;
            i++) { //如果不是回文,直接判错
        if (str[i] != str[str.length() - 1 - i])
            return 0;
    }
    return 1;
}
int f_front(string ss) {
    map<string, int>    mp;   //用于标记字符串是否出现过
    int ans = 0;
    string temp;
    for (int i = 0; i < ss.length(); i++) { //枚举子串起点
        for (int j = 1; j <= ss.length(); j++) { //枚举子串长度
            temp = ss.substr(i, j);
            if (check(temp)
                    && !mp.count(
                        temp)) { //如果这个串是正回文并且没有出现过
                mp[temp] = 1;
                ans++;
            }
        }
    }
    return ans;
}
int f_rear(string ss) {
    map<string, int>    mp;   //用于标记字符串是否出现过
    int ans = 0;
    string temp;
    for (int i = 0; i < ss.length(); i++) { //枚举子串起点
        for (int j = 1; j <= ss.length(); j++) { //枚举子串长度
            temp = ss.substr(i, j);
            if (!check(temp)
                    && !mp.count(
                        temp)) { //如果这个串不是正回文并且没有出现过
                mp[temp] = 1;
                ans++;
            }
        }
    }
    return ans;
}
void sovle() {
    cin >> n >> s;
    for (int i = 1; i <= s.length() - 1; i++) { //枚举前串的长度
        a = s.substr(0, i); //前串,起点是0,长度是i
        b = s.substr(i, s.length() -
                     i); //后串,起点是i,长度是length-i
        //cout << "前串:"+a << endl <<"后串:"+ b <<endl;
        A = f_front(a);
        B = f_rear(b);
        if (A * B > max_ans) {
            max_ans = A * B;
        }
    }
    cout << max_ans << endl;
}
posted @ 2021-05-18 21:28  RioTian  阅读(225)  评论(1编辑  收藏  举报