You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

第十四届蓝桥杯大赛青少组全国总决赛初级组C++题解

第一题

给定一个十进制正整数N1≤N≤10^9,请从小到大输出1~N之间(含1N)所有满足以下要求的数:

  1. 这个数转换为八进制后是一个回文数;

  2. 这个数是一个平方数。

例如:N = 20,在1~20之间满足要求的数有1、4、9,因为有,

1转换为八进制为1,是一个回文数;且1 = 1^2,是一个平方数;

4转换为八进制为4,是一个回文数;且4 = 2^2,是一个平方数;

9转换为八进制为11,是一个回文数;且9 = 3^2,是一个平方数。

故输出1 4 9

输入描述

输入一个十进制正整数N1≤N≤10^9

输出描述

输出一行,包含若干个十进制正整数,表示满足题目要求的数。结果从小到大输出,两个正整数之间用一个空格隔开

样例输入

20

样例输出

1 4 9

题目解析

首先定义两个辅助函数:is_octal_palindrome用于检查一个数转换为八进制后是否为回文数,is_square用于检查一个数是否为平方数。在main函数中,我们遍历1n之间的所有整数,检查它们是否满足题目要求,如果满足则输出

Code

#include <bits/stdc++.h>
using namespace std;

// 判断8进制回文
bool is_octal_palindrome(int n) {
    string s1 = "";
    while (n) {
        s1 += to_string(n % 8);
        n /= 8;
    }
    string s2 = s1;
    reverse(s2.begin(), s2.end());
    return s1 == s2;
}

// 判断完全平方数
bool is_square(int n) {
    int s = (int)sqrt(n);
    return s * s == n;
}

int main() {
    int n;
    cin >> n;

    for (int i = 1; i <= n; i++)
        if (is_octal_palindrome(i) && is_square(i))
            cout << i << " ";
    return 0;
}

由于n的数据范围太大,最多1e9,所以,很明显O(N)的复杂度也会TLE掉一些测试样例,实测83分,需要进一步优化,你个蓝桥杯,还是初级组,还是第一题,上来就这么难是不是有点过了??

为了优化代码,我们可以减少遍历的次数。因为我们要找的数是平方数,所以我们只需要遍历1sqrt(n)之间的整数,然后检查它们的平方是否满足八进制回文数的条件。这样可以显著减少遍历次数。以下是优化后的代码:

Code

#include <bits/stdc++.h>
using namespace std;

bool is_octal_palindrome(int n) {
    string s1 = "";
    while (n) {
        s1 += to_string(n % 8);
        n /= 8;
    }
    string s2 = s1;
    reverse(s2.begin(), s2.end());
    return s1 == s2;
}

int main() {
    int n;
    cin >> n;

    int s = (int)sqrt(n);
    for (int i = 1; i <= s; i++) {
        int s2 = i * i;
        if (is_octal_palindrome(s2)) cout << s2 << " ";
    }
    return 0;
}

第二题

从金星探测器传回来一组测量数据,这是一个长度为N1≤N≤1000000的整数数列,数列中的每个整数代表某一种化学成分(相同的整数代表相同的化学成分)。

主要成分:指在包含的所有化学成分中比例超过一半(N÷2的结果向下取整)的成分。

现在要判断其是否有主要成分;如果有,其主要成分是哪一种?

例如:

N=7,整数数列为1 2 3 2 2 1 2,其中成分24个,超过了7的一半(7的一半向下取整为3),所以主要成分是2

N=6,整数数列为1 102 31 31 1 102,其中的每一种成分都只有2个,未超过6的一半(6的一半为3),所以没有主要成分。

输入描述

第一行输入一个正整数N1≤N≤1000000,表示数列长度

第二行输入N个整数(1≤整数≤2×10^9),每个整数表示一种化学成分,两个整数之间用一个空格隔开

输出描述

输出一行,如果存在主要成分,则输出代表主要成分的整数,否则,输出No

样例输入

7 1 2 3 2 2 1 2 

样例输出

2

题目解析

好多孩子一看这题简单啊,就是一个桶嘛,所以很快就有了下面的代码:

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 10;
int a[N];
int b[N]; // 开1e6不够开2e9就会MLE用桶是不行的
int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i], b[a[i]]++;
    bool flag = false;
    for (int i = 1; i <= n; i++)
        if (b[a[i]] > n / 2) {
            cout << a[i];
            flag = true;
            break;
        }

    if (!flag) cout << "No";

    return 0;
}

但是,你看看数据范围啊,它的整数上限值是2\times 10^9,如果按这个来设计桶的话,那肯定MLE啊! 需要Hash表处理,不能直接用桶来模拟:

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 10;
int a[N];
unordered_map<int, int> _map;
int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i], _map[a[i]]++;
    bool flag = false;
    for (int i = 1; i <= n; i++)
        if (_map[a[i]] > n / 2) {
            cout << a[i];
            flag = true;
            break;
        }

    if (!flag) cout << "No";

    return 0;
}

第三题

编程实现:简单算术题

题目描述

给定一道 没有括号四则混合运算算术题(可能包含多余的空格),请编程计算出结果。

运算规则如下:

既有乘、除法又有加、减法的,要先算乘除法,再算加减法

同级运算时,要从左往右按顺序计算

所有除法运算的结果都只保留整数部分(直接舍弃小数部分)

例如:当算术题为 2 + 3*4 - 10/6 + 1/24时,优先计算乘除法,有3*4=1210/6=11/24=0;然后再计算加减法,2+3*4-10/6+1/24=2+12-1+0=13,故输出13

输入描述

输入一个字符串,表示算术题,5≤字符串长度≤100000,字符串中只包含数字字符以及“+”、“-”、“*”、“/”运算符,不含括号可能包含空格

算式中的运算数范围:1≤运算数≤200

输出描述

输出一个整数,表示算术题的计算结果。

题目数据保证算式的每一步运算的结果都在-2×10^9~2×10^9之间。

样例输入

2+3*4-10/6+1/24 

样例输出

13

这就是一道用两个栈(运算符栈,数字栈)的表达式求值问题的裸题,如果讲过,孩子的电脑里有这道题,可以轻松AC,否则,就是0分。

Code

#include <bits/stdc++.h>
using namespace std;

stack<int> stk;
stack<char> op;

unordered_map<char, int> g{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};

void eval() {
    int a = stk.top();
    stk.pop();

    int b = stk.top();
    stk.pop();

    char p = op.top();
    op.pop();

    int r;

    if (p == '+') r = b + a;
    if (p == '-') r = b - a;
    if (p == '*') r = b * a;
    if (p == '/') r = b / a;

    stk.push(r);
}

int main() {
    string s;
    getline(cin, s);

    for (int i = 0; i < s.size(); i++) {
        if (s[i] == ' ') continue; // 过滤掉空格
        if (isdigit(s[i])) {
            int x = 0;
            while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
                x = x * 10 + s[i] - '0';
                i++;
            }
            i--;

            stk.push(x);
        }

        else if (s[i] == '(')
            op.push(s[i]);
        else if (s[i] == ')') {
            while (op.top() != '(') eval();
            op.pop();
        } else {
            while (op.size() && g[op.top()] >= g[s[i]]) eval();
            op.push(s[i]);
        }
    }
    while (op.size()) eval();

    printf("%d\n", stk.top());
    return 0;
}

第四题

编程实现:数独填数

数独是源自18世纪瑞士的一种数学游戏。玩家需要根据9×9网格上的已知数字,将剩余的所有空格填上数字,使得9×9网格上每一行、每一列及每一个3×3方块(粗线)内的数字均包含1~9,并且数字不重复。

这个数独可以使用如下9×9的字符方阵表示(空格用“.”表示):

输入样例

17.5..8..
.52.1....
.....759.
.8...94.3
.197.4..8
7......15
4.1...6..
3...2..59
...96..3.

输出样例

174593826
952816347
638247591
286159473
519734268
743682915
491375682
367428159
825961734

Code

回溯法,学过,电脑里有,就是轻松AC,否则,累死也白扯

#include <bits/stdc++.h>
using namespace std;

const int N = 9;
int g[N][N];

int isValid(int r, int c, int x) {
    // 同行同列是不是存在同样的数字x
    for (int i = 0; i < 9; i++)
        if (g[r][i] == x || g[i][c] == x) return 0;

    // 找出3X3的小方块开始行和开始列,也就是所在块左上角坐标
    int row = r / 3 * 3;
    int col = c / 3 * 3;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            if (g[row + i][col + j] == x)
                return 0;
    return 1;
}

int dfs() {
    for (int r = 0; r < 9; r++) {
        for (int c = 0; c < 9; c++) {
            if (g[r][c] == 0) {
                for (int x = 1; x <= 9; x++) {
                    if (isValid(r, c, x)) {
                        g[r][c] = x;
                        if (dfs()) return 1;
                        g[r][c] = 0;
                    }
                }
                return 0;
            }
        }
    }
    return 1;
}

int main() {
    for (int i = 0; i < 9; i++)
        for (int j = 0; j < 9; j++) {
            char x;
            cin >> x;
            if (x != '.') g[i][j] = x - '0';
        }

    dfs();

    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) cout << g[i][j];
        cout << endl;
    }
    return 0;
}

第五题

编程实现:数学实验

题目描述

老师在黑板上写出了一个正整数数列,让所有同学都来做一个数学实验,要求如下:

这组数总共不超过500000个,每个数的大小范围在1~80之间;

要从这组数中找出两个相邻且相同的数,删掉其中一个数,剩下的一个数加1(例如:两个相邻的6,变成一个7

重复执行第2步;

当操作无法继续进行时,实验结束,此时,实验结果就是这组数里面最大的数

注意:不同的实验方案得到的最大数不同。

现在给定了一个正整数数列,请你编写程序计算出能够得到的实验结果最大是多少。

例如:

N=6,这个正整数数列是 1、2、2、2、3、4时,得到最大数的方法如下:

先将后面两个2变成一个3,然后33变成4,最后44变成5。可以证明,没有其它更好的方案,故输出5

输入描述

第一行输入一个正整数N1≤N≤500000

第二行输入N个正整数(1≤正整数≤80),相邻两个数之间用一个空格隔开

输出描述

输出一个正整数,表示实验结束后能够得到的最大的实验结果

样例输入

6 1 2 2 2 3 4 

样例输出

5

题解

注:不是连着两个一样就一定要变更+1也可能保持不动保持不动有时会有更好的效果。所以这是一个动态规划的题目不是贪心的题目。

考虑DP

状态表示

  • 尝试一维状态f[i]

f[i]:前i个数字最终可以获取的最大值

思考一下f[i-1]f[i]之间的关系 此时,我们会发现,无法表示 相邻变更不变更,状态不够清晰,需要把状态细化。

  • 尝试二维状态:f[i][j]

f[i][j]:前i个数字,经过变更操作后,最后一个数字是j,可以获得的最大值

:因为最后一个数字是什么非常重要,是我们不断递推的基础,所以,给它设计了一个维度表示。

问题来了:因为最后一位是j,可能是原来不是,是因为两个相邻一样的数字拼出来的j,也可能是原始的j,这两个都是可能的,都应该保留下来,为后面的递推做地基,现在,我们把它们两个混淆了,就相当于损毁了一个,导致后续的递推缺少了基础,明显也是不对的。

:当出现连续一样的数字时,我们可以选择直接拼掉,也可以选择不处理,这是两种情况,我们应该明确两种表示法,否则就会混淆。

  • 尝试三维状态:f[i][j][k]

f[i][j][k]:前i个数字,最后一个数字是j,状态是k时可以取得的最大值 ① k=0:原来就是j,没有拼掉 ② k=1:相邻后相等+1等到的j

这时,我们发现状态还是非常清晰的,任何一个状态的来龙去脉都可以不重不漏的描述清楚,没有丢失状态。

状态转移

(1) 当我们位于第i个数字面前时,表示前i-1个已经处理完毕,对应的DP都已经填充利索了。

(2) 如果当前数字k=a[i],不管与前一位的数字j是不是相等,最起码可以从前一位数字的状态转移数值:

\large f[i][a[i]][0]=max({f[i][a[i]][0],f[i-1][j][0],f[i-1][j][1]})

(3)如果当前数字a[i],与前一位的数字j相等(这个j可能是变异过,也可能是原始的),那么与相位相邻可以产生+1的效果,有可能会使得最大值更新。

\large f[i][k + 1][1] = max({f[i][k + 1][0],f[i][k + 1][1], f[i - 1][j][0], f[i - 1][j][1],  j + 1})

答案

走完前n个数字,并且,枚举每一个可能结尾的数字j,变更或不变更的都可能是答案,枚举一次即可。

/*
测试用例I
6
1 2 2 2 3 4
答案
5

测试用例II
6
1 2 2 1 3 4
答案
5

测试用例III
5
2 1 2 1 1
答案
3

测试用例IV
4
80 79 79 80
答案
81


测试用例V
6
2 3 4 4 4 5
答案
6

测试用例VI
6
2 3 4 6 5 5
答案
7

*/

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 500010;
int a[N]; // 原数列

// DP数组
int f[N][82][2];

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    // 初始化前i个数字中以a[i]结尾的状态没有经历变更最大值初始化为a[i]
    for (int i = 1; i <= n; i++) f[i][a[i]][0] = a[i];

    // DP
    // 以每个数字位置为阶段
    for (int i = 2; i <= n; i++) {
        int k = a[i];
        for (int j = 1; j <= 81; j++) { // 枚举每个可能的填充值
            // 每一个前序可能状态都可以通过加入a[i]时更新到新的状态
            f[i][k][0] = max({f[i][k][0], f[i - 1][j][0], f[i - 1][j][1], k});
            // 如果发现相邻且相等的情况,合并后+1
            if (k == j)
                f[i][k + 1][1] = max({f[i][k + 1][1], k + 1});
        }
    }
    // 结果
    int res = 0;

    for (int i = 1; i <= 81; i++)
        res = max({res, f[n][i][0], f[n][i][1]});

    cout << res << endl;
    return 0;
}