583 lines
16 KiB
Markdown
583 lines
16 KiB
Markdown
## 第十四届蓝桥杯大赛青少组全国总决赛初级组$C++$题解
|
||
### 第一题
|
||
|
||
给定一个十进制正整数$N(1≤N≤10^9)$,请从小到大输出$1$~$N$之间(含$1$和$N$)所有满足以下要求的数:
|
||
|
||
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`
|
||
|
||
**输入描述**
|
||
|
||
输入一个十进制正整数$N(1≤N≤10^9)$
|
||
|
||
|
||
**输出描述**
|
||
|
||
输出一行,包含若干个十进制正整数,表示满足题目要求的数。结果从小到大输出,两个正整数之间用一个空格隔开
|
||
|
||
**样例输入**
|
||
```cpp {.line-numbers}
|
||
20
|
||
```
|
||
|
||
|
||
**样例输出**
|
||
```cpp {.line-numbers}
|
||
1 4 9
|
||
```
|
||
|
||
### 题目解析
|
||
|
||
首先定义两个辅助函数:`is_octal_palindrome`用于检查一个数转换为八进制后是否为回文数,`is_square`用于检查一个数是否为平方数。在`main`函数中,我们遍历`1`到`n`之间的所有整数,检查它们是否满足题目要求,如果满足则输出
|
||
|
||
#### $Code$
|
||
```cpp {.line-numbers}
|
||
#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$分,需要进一步优化,你个蓝桥杯,还是初级组,还是第一题,上来就这么难是不是有点过了??
|
||
|
||
为了优化代码,我们可以减少遍历的次数。因为我们要找的数是平方数,所以我们只需要遍历$1$到$sqrt(n)$之间的整数,然后检查它们的平方是否满足八进制回文数的条件。这样可以显著减少遍历次数。以下是优化后的代码:
|
||
|
||
#### $Code$
|
||
```cpp {.line-numbers}
|
||
#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;
|
||
}
|
||
```
|
||
|
||
### 第二题
|
||
|
||
从金星探测器传回来一组测量数据,这是一个长度为$N(1≤N≤1000000)$的整数数列,数列中的每个整数代表某一种化学成分(相同的整数代表相同的化学成分)。
|
||
|
||
主要成分:指在包含的所有化学成分中比例超过一半($N÷2$的结果向下取整)的成分。
|
||
|
||
现在要判断其是否有主要成分;如果有,其主要成分是哪一种?
|
||
|
||
例如:
|
||
|
||
当$N=7$,整数数列为`1 2 3 2 2 1 2`,其中成分$2$有$4$个,超过了$7$的一半($7$的一半向下取整为$3$),所以主要成分是$2$。
|
||
|
||
当$N=6$,整数数列为`1 102 31 31 1 102`,其中的每一种成分都只有$2$个,未超过$6$的一半($6$的一半为$3$),所以没有主要成分。
|
||
|
||
**输入描述**
|
||
|
||
第一行输入一个正整数$N(1≤N≤1000000)$,表示数列长度
|
||
|
||
第二行输入$N$个整数($1≤$整数$≤2×10^9$),每个整数表示一种化学成分,两个整数之间用一个空格隔开
|
||
|
||
**输出描述**
|
||
|
||
输出一行,如果存在主要成分,则输出代表主要成分的整数,否则,输出`No`
|
||
|
||
**样例输入**
|
||
```cpp {.line-numbers}
|
||
7 1 2 3 2 2 1 2
|
||
```
|
||
|
||
**样例输出**
|
||
```cpp {.line-numbers}
|
||
2
|
||
```
|
||
|
||
### 题目解析
|
||
好多孩子一看这题简单啊,就是一个桶嘛,所以很快就有了下面的代码:
|
||
|
||
#### $Code$
|
||
```cpp {.line-numbers}
|
||
#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$
|
||
```cpp {.line-numbers}
|
||
#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=12,10/6=1,1/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$之间。
|
||
|
||
**样例输入**
|
||
```cpp {.line-numbers}
|
||
2+3*4-10/6+1/24
|
||
```
|
||
|
||
**样例输出**
|
||
```cpp {.line-numbers}
|
||
13
|
||
```
|
||
|
||
这就是一道用两个栈(运算符栈,数字栈)的表达式求值问题的裸题,如果讲过,孩子的电脑里有这道题,可以轻松$AC$,否则,就是$0$分。
|
||
|
||
#### $Code$
|
||
```cpp {.line-numbers}
|
||
#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$的字符方阵表示(空格用“.”表示):
|
||
|
||
|
||
**输入样例**
|
||
```cpp {.line-numbers}
|
||
17.5..8..
|
||
.52.1....
|
||
.....759.
|
||
.8...94.3
|
||
.197.4..8
|
||
7......15
|
||
4.1...6..
|
||
3...2..59
|
||
...96..3.
|
||
```
|
||
|
||
**输出样例**
|
||
```cpp {.line-numbers}
|
||
174593826
|
||
952816347
|
||
638247591
|
||
286159473
|
||
519734268
|
||
743682915
|
||
491375682
|
||
367428159
|
||
825961734
|
||
```
|
||
|
||
|
||
#### $Code$
|
||
回溯法,学过,电脑里有,就是轻松$AC$,否则,累死也白扯
|
||
|
||
```cpp {.line-numbers}
|
||
#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$,然后$3$和$3$变成$4$,最后$4$和$4$变成$5$。可以证明,没有其它更好的方案,故输出$5$。
|
||
|
||
|
||
|
||
**输入描述**
|
||
|
||
第一行输入一个正整数$N(1≤N≤500000)$
|
||
|
||
第二行输入$N$个正整数($1$≤正整数≤$80$),相邻两个数之间用一个空格隔开
|
||
|
||
|
||
|
||
**输出描述**
|
||
|
||
输出一个正整数,表示实验结束后能够得到的最大的实验结果
|
||
|
||
|
||
**样例输入**
|
||
```cpp {.line-numbers}
|
||
6 1 2 2 2 3 4
|
||
```
|
||
|
||
**样例输出**
|
||
```cpp {.line-numbers}
|
||
5
|
||
```
|
||
|
||
**题解**:
|
||
|
||
><font color='red' size=4><b>注:不是连着两个一样就一定要变更+1,也可能保持不动,保持不动有时会有更好的效果。所以,这是一个动态规划的题目,不是贪心的题目。</b></font>
|
||
|
||
考虑$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$,变更或不变更的都可能是答案,枚举一次即可。
|
||
|
||
```cpp {.line-numbers}
|
||
/*
|
||
测试用例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$
|
||
```cpp {.line-numbers}
|
||
#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;
|
||
}
|
||
``` |