这里借用华东理工大学的OJ,所有题目均可在上面提交。
试题 A: 组队 5 分
题目链接:http://oj.ecustacm.cn/problem.php?id=1462
题意:作为篮球队教练,你需要从以下名单中选出 1 号位至 5 号位各一名球员,组成球队的首发阵容。每位球员担任1号位至5号位时的评分如下表所示。请你计算首发阵容1号位至5号位的评分之和最大可能是多少?
思路:在纸上划拉划拉。
答案:490
试题 B: 年号字串 5 分
题目链接:http://oj.ecustacm.cn/problem.php?id=1463
题意:小明用字母 A 对应数字 1,B 对应 2,以此类推,用 Z 对应 26。对于 27 以上的数字,小明用两位或更长位的字符串来对应,例如 AA 对应 27,AB 对应 28,AZ 对应 52,LQ 对应 329。请问 2019 对应的字符串是什么?
思路:在纸上划拉划拉。
答案:BYQ
错误代码分享:
#include
#include
#include
using namespace std;
typedef long long ll;
void solve(int n){
if(n==0) return;
solve(n / 26);
printf("%c", n % 26 + 64);
}
int main(){
solve(2019);
//solve(26);
return 0;
}
正确代码:
上面代码的细节处理有错误,像 这种进位边界值单模是有问题的。 代表 ,单模的话会变成 ,这样 就出不来了,而且 , 又代表 ,这样不但 出不来,而且会多出一个 。
#include
void solve(int n){
if(n==0) return; // 除至末尾,结束递归
if(n % 26 == 0)
{
solve((n-1)/26); // 被26整除不需要进位,因此用(n-1)递归
printf("Z"); // 被26整除单独处理
}
else
{
solve(n/26); // 进制转换的正常处理算法
printf("%c", n % 26 + 'A' - 1); // 余数即当前位数值
}
}
int main(){
solve(2019); //BYQ
//solve(26);
return 0;
}
试题 C: 数列求值 10分
题目链接:http://oj.ecustacm.cn/problem.php?id=1453
题意:给定数列 1, 1, 1, 3, 5, 9, 17, …,从第 4 项开始,每项都是前 3 项的和。求 第 20190324 项的最后 4 位数字。
思路:在循环求解的过程中,每一项都模10000。
答案:4659
代码如下:
#include
#include
#include
using namespace std;
typedef long long ll;
int main(){
int a = 1, b = 1, c = 1, d;
for(int i = 4; i <= 20190324; ++i)
{
d = (a + b + c) % 10000;
a = b % 10000;
b = c % 10000;
c = d % 10000;
}
printf("%d", d); //4659
return 0;
}
试题 D: 数的分解 10分
题目链接:http://oj.ecustacm.cn/problem.php?id=1464
题意:把 2019 分解成 3 个各不相同的正整数之和,并且要求每个正整数都不包含数字 2 和 4,一共有多少种不同的分解方法?注意交换 3 个整数的顺序被视为同一种方法,例如 1000+1001+18 和 1001+1000+18 被视为同一种。
思路:防止重复,默认顺序为递增。
答案:40785
代码如下:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
bool check(int n){
while(n)
{
int tmp = n % 10;
if(tmp == 2 || tmp == 4) return false;
n /= 10;
}
return true;
}
int main(){
int ans = 0;
for(int i = 1; i 2019; ++i)
{
if(check(i))
{
for(int j = i + 1; j 2019; ++j)
{
if(check(j))
{
int k = 2019 - i - j;
if(check(k) && k > j) ans++;
}
}
}
}
printf("%d", ans); //40785
return 0;
}
试题 E: 迷宫 15分
题目链接:http://oj.ecustacm.cn/problem.php?id=1455
题意:给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到它的上、下、左、右四个方向之一。D、U、L、R 分别表示向下、向上、向左、向右走。对于下面这个更复杂的迷宫(30 行 50 列),请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。请注意在字典序中 D
解法一:excel
1.用tab键将maze.txt里的01字符分隔开。不想手敲tab键的话,可以使用记事本的替换功能。
2.连同tab键的空白一起复制进excel里。适当调整单元格的列宽和行高,使其看起来像正方形。
3.将 1 单元格的背景颜色全部替换为蓝色。还好,迷宫设计的陷阱不算多。
解法二:BFS
要求字典序最小的答案,考虑在BFS的过程中,如果向下有最短路径,那就不需要向左/向右/向上了,如果向下没有最短路径,那就再考虑向左是否可以 ... 以此类推 ... ,因此,问题的关键就是如何判断当前位置向下/向左/向右/向上是否有最短路径呢?
我们可以在求解字典序最小的路径之前,首先倒着跑一遍BFS,也就是在从终点到起点BFS的过程中,记录每个位置到终点的最短距离 。
于是,经过这样的预处理之后,在正向求解字典序最小的路径时,就可以用 来判断当前位置向下/向左/向右/向上是否有最短路径,从而循环下去,完成求解。
下面代码运行超时,不过最终答案(保存在out.txt中)是正确的。
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int dx[4] = {1, 0, 0, -1};
const int dy[4] = {0, -1, 1, 0};
const char dir[4] = {'D', 'L', 'R', 'U'};
const int N = 55;
string g[N];
int dist[N][N];
struct node {
int x, y;
node(int xx, int yy):x(xx),y(yy) { }
};
void bfs(){
memset(dist, -1, sizeof(dist));
queue q;
dist[29][49] = 0;
q.push(node(29,49));while(!q.empty())
{
node t = q.front();
q.pop();for(int i = 0; i 4; ++i)
{int nx = t.x + dx[i];int ny = t.y + dy[i];if(nx >= 0 && nx 30 && ny >= 0 && ny 50 && dist[nx][ny] == -1 && g[nx][ny]=='0')
{
dist[nx][ny] = dist[t.x][t.y] + 1;
q.push(node(nx,ny));
}
}
}
}int main(){
freopen("maze.txt", "r", stdin);
freopen("out.txt", "w", stdout);for(int i = 0; i 30; ++i) // 30行50列cin>>g[i];
bfs(); // 倒着广搜一遍,得到每个点到终点的最短距离dist[x][y] //cout<string ans; // 记录路径 int x = 0, y = 0;while(x != 29 || y != 49) // 注意这里是:或
{for(int i = 0; i 4; ++i)
{int nx = x + dx[i];int ny = y + dy[i];if(nx >= 0 && nx 30 && ny >= 0 && ny 50 && g[nx][ny]=='0')
{if(dist[x][y] == dist[nx][ny] + 1)
{
x = nx;
y = ny;
ans += dir[i];break; // break
}
}
}
}//cout<cout<return 0;
}
答案:
DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR
试题 F: 特别数的和 15分
题目链接:http://oj.ecustacm.cn/problem.php?id=1465
题意:小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0)。在 1 到 40 中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574。请问,在 1 到 n 中,所有这样的数的和是多少?
思路:判断 中每一个数是否符合条件,若符合条件累加起来。
AC代码:
#include
#include
#include
#include
#include
using namespace std;
bool judge(int x){
while(x)
{
int t = x % 10;
if(t == 2 || t == 0 || t == 1 || t == 9) return true;
x /= 10;
}
return false;
}
int main(){
int n;
scanf("%d", &n);
int sum = 0;
for(int i = 1; i <= n; ++i)
if(judge(i)) sum += i;
printf("%d\n", sum);
return 0;
}
试题 G: 完全二叉树的权值 20分
题目链接:http://oj.ecustacm.cn/problem.php?id=1457
题意:给定一棵包含 N 个节点的完全二叉树,树上每个节点都有一个权值,按从 上到下、从左到右的顺序依次是 ,现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?如果有多个深度的权值和同为最大,请你输出其中最小的深度。注:根的深度是 1。
注意:本题是完全二叉树不是满二叉树。最后一层的权值和会爆掉int。
AC代码:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 100010;
int n, a[N];
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
ll maxn = -1e9;
int maxl = -1e9;
int i = 1, level = 1;
while(i <= n)
{
ll t = 0;
int s = 1 <1);
while(i <= n && s--) t += (ll)a[i++];
if(t > maxn) maxn = t, maxl = level;
level++;
}
printf("%d\n", maxl);
return 0;
}
试题 H: 等差数列 20分
题目链接:http://oj.ecustacm.cn/problem.php?id=1466
题意:数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列,只记得其中 N 个整数。现在给出这 N 个整数,小明想知道包含这 N 个整数的最短的等差数列有几项?
思路:先排个序,于是,对于该递增等差数列来说,首项 和 尾项 便固定了。
项数 可知,公差 越大,项数越小。
因此,问题就转变成了:求满足递增等差条件的最大公差 是多少。(我的第一反应是二分,但捣鼓半天没捣鼓出来)
换个思路,满足条件的最大公差 其实就是数列中所有相邻两项之差的最大公约数。
注意:需要特判公差为 的情况
AC代码:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 100010;
int n, a[N], b[N];
int gcd(int a, int b){
return b==0 ? a : gcd(b, a%b);
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
int cnt = 0;
for(int i = 2; i <= n; ++i)
b[++cnt] = a[i] - a[i-1];
sort(b + 1, b + cnt + 1);
if(b[1] == 0)
{
printf("%d", n);
return 0;
}
int d = b[1];
for(int i = 2; i <= cnt; ++i)
d = gcd(d, b[i]);
printf("%d\n", ((a[n] - a[1]) / d) + 1);
return 0;
}
试题 I: 后缀表达式 25分
题目链接:http://oj.ecustacm.cn/problem.php?id=1467
题意:给定 N 个加号、M 个减号以及 N + M + 1 个整数 。小明想知道在所有由这 N 个加号、M 个减号以及 N + M + 1 个整数凑出的合法的后缀表达式中,结果最大的是哪一个?请你输出这个最大的结果。例如使用 1 2 3 + -,则 "2 3 + 1 -" 这个后缀表达式结果是 4,是最大的。
去年不会的题,今年还是不会。
思路参考:https://blog.csdn.net/tlyzxc/article/details/104417212/
逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)
一个表达式 的后缀形式可以如下定义:
- 如果 是一个变量或常量,则 的后缀式是 本身。
- 如果 是 形式的表达式,这里 是任何二元操作符,则 的后缀式为 ,这里 和 分别为 和 的后缀式。
- 如果 是()形式的表达式,则 的后缀式就是 的后缀式。
例如, 的后缀表达式为:→ → → →
因此,这道题贪心的思路并不仅限于数字顺序的问题,还可以加括号,例如:
0 2 1 2 3
不对, 正确
1 2 1 2 3 4
正确的顺序应该是
也就是说,最少可以只有一个减号起作用,其余的减号全部负负得正。
题目的数据范围里面可以有负数,后缀表达式实际上是隐藏括号的,我们也可借助这一特点来变负为正。
1 1 -3 -2 -1
不对,实际情况应该是
所以,这里我们要根据是否有负数存在以及负数和总个数的关系进行条件判断:
1.如果负数个数不等于总个数
3 3 -1 -1 2 2 2 2 2
可以写成
最后负数全部能转换为正数
2.如果负数个数等于总个数
3 3 -2 -1 -1 -1 -1 -1 -1
应该是
此时,因为一共有 个正负号, 个数字,所以,必然有一个负数不能被转换为正数,根据贪心的理论,这个数字的绝对值应该最小,即负数最大。
综上,AC代码:
#include
#include
#include
using namespace std;
typedef long long ll;
int a[200010];
int main(){
int n, m; //n个加号、m个减号
scanf("%d%d", &n, &m);
int k = n + m + 1;
ll sum = 0;
int neg = 0;
for(int i = 0; i {
scanf("%d", &a[i]);
sum += a[i];
if(a[i] 0) neg++; //统计负数的个数
}
if(!m) //如果没有减号,那么最大值就是sum
{
printf("%lld\n", sum);
return 0;
}
sort(a, a + k); //从小到大排序
if(neg) //判断是否有负数
{
if (neg == k) //判断负数个数和总个数的关系
{
for (int i = 0; i 1; i++) //a[neg-1]为最大负数,即绝对值最小值
sum -= 2 * a[i];
}
else
{
for (int i = 0; i sum -= 2 * a[i];
}
}
else
{
sum -= 2 * a[0];
}
printf("%lld\n", sum);
return 0;
}