第七届蓝桥杯本科B组省赛

Posted _osayes_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第七届蓝桥杯本科B组省赛相关的知识,希望对你有一定的参考价值。

煤球数目

有一堆煤球,堆成三角棱锥形。具体:
第一层放1个,
第二层3个(排列成三角形),
第三层6个(排列成三角形),
第四层10个(排列成三角形),
….
如果一共有100层,共有多少个煤球?

请填表示煤球总数目的数字。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

思路:

推出第i层有i*(i+1)/2个煤球,累加到100层即可,答案为171700

#include <bits/stdc++.h>
using namespace std;
int main() {
    int ans = 0;
    for (int i = 1; i <= 100; i++) {
        ans += i * (i + 1) / 2;
    }
    cout << ans << endl;
    return 0;
}

生日蜡烛

某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。

现在算起来,他一共吹熄了236根蜡烛。

请问,他从多少岁开始过生日party的?

请填写他开始过生日party的年龄数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

思路

根据题目意思,假设他从x岁开始过生日,现在是y岁,这么多年的岁数和是236,由于每一年年龄会加1,相当于是一个项数位(y-x+1),公差为1的等差数列求和。于是只要枚举x和y,然后根据公式算一下(直接累加求和也是可以的)是不是等于236就行了。答案等于26。

#include <bits/stdc++.h>
using namespace std;
int main() {
    for (int i = 1; i <= 100; i++) {
        for (int j = i + 1; j <= 100; j++) {
            if ((i + j) * (j - i + 1) / 2 == 236) cout << i << endl;
        }
    }
    return 0;
}

凑算式

    B   DEF
A + - + --- = 10
    C   GHI

这个算式中A~I代表1~9的数字,不同的字母代表不同的数字。

比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。

这个算式一共有多少种解法?

注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。

思路

利用全排列生成所有A~I和1~9可能的对应情况,对每一种对应情况判断一下等式是否成立即可。答案是29。(当时比赛的时候逗比了,看见”/”就以为必须是整除,所以代码里多了个B%C==0&&DEF%GHI==0)

#include <bits/stdc++.h>
using namespace std;
int a[10], ans;
int main() {
    for (int i = 1; i <= 9; i++) {
        a[i] = i;
    }
    do {
        double A = a[1];
        double B = a[2];
        double C = a[3];
        double DEF = a[4] * 100 + a[5] * 10 + a[6];
        double GHI = a[7] * 100 + a[8] * 10 + a[9];
        if (fabs(A + B / C + DEF / GHI - 10) < 1e-12) ans++;
    } while (next_permutation(a + 1, a + 10));
    cout << ans << endl;
    return 0;
}

快速排序

排序在各种场合经常被用到。
快速排序是十分常用的高效率的算法。

其思想是:先选一个“标尺”,
用它把整个队列过一遍筛子,
以保证:其左边的元素都不大于它,其右边的元素都不小于它。

这样,排序问题就被分割为两个子区间。
再分别对子区间排序就可以了。

下面的代码是一种实现,请分析并填写划线部分缺少的代码。

#include <stdio.h>

void swap(int a[], int i, int j) {
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}


int partition(int a[], int p, int r) {
    int i = p;
    int j = r + 1;
    int x = a[p];
    while(1) {
        while(i < r && a[++i] < x);
        while(a[--j] > x);
        if(i >= j) break;
        swap(a, i, j);
    }
    ______________________;
    return j;
}


void quicksort(int a[], int p, int r) {
    if(p < r) {
        int q = partition(a, p, r);
        quicksort(a, p, q - 1);
        quicksort(a, q + 1, r);
    }
}

int main() {
    int i;
    int a[] = {5, 13, 6, 24, 2, 8, 19, 27, 6, 12, 1, 17};
    int N = 12;

    quicksort(a, 0, N - 1);

    for(i = 0; i < N; i++) printf("%d ", a[i]);
    printf("\n");

    return 0;
}

注意:只填写缺少的内容,不要书写任何题面已有代码或说明性文字。

思路

相信对每一位计算机相关的考研党和学霸来说这种题目都是小case吧。。。根据quicksort的参数和main函数的调用,以及partition里面的参数可以猜到partition的参数a[],p,r的含义是要排序的数组、开始位置、结束位置,那么partition的功能就是将a数组的p位置到r位置的序列划分成两半,让左一半全部小于等于右一半,同时返回那个临界值的位置。在partition里面,将a[p]选为了参考值(int x=a[p]),而后这个a[p]就再没变化,这显然是不行的,因为参考值放在了最前面,比它小的只能放后面。所以还缺了“将a[p]这个临界值换到后面去”这一步。换到哪里呢?由于后面return的是j,那么答案就出来了,swap(a, p, j)。填进代码里面测试,对或错就知道了,如果还不放心,用rand()给a数组赋值,排完序再打出来看看。

抽签

X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。
C国最多可以派出2人。
….

那么最终派往W星的观察团会有多少种国别的不同组合呢?

下面的程序解决了这个问题。
数组a[] 中既是每个国家可以派出的最多的名额。
程序执行结果为:
DEFFF
CEFFF
CDFFF
CDEFF
CCFFF
CCEFF
CCDFF
CCDEF
BEFFF
BDFFF
BDEFF
BCFFF
BCEFF
BCDFF
BCDEF
….
(以下省略,总共101行)

#include <stdio.h>
#define N 6
#define M 5
#define BUF 1024


void f(int a[], int k, int m, char b[]) {
    int i, j;

    if(k == N) {
        b[M] = 0;
        if(m == 0) printf("%s\n", b);
        return;
    }

    for(i = 0; i <= a[k]; i++) {
        for(j = 0; j < i; j++) b[M - m + j] = k + ‘A‘;
        ______________________; //填空位置
    }
}
int main() {
    int a[N] = {4, 2, 2, 1, 1, 3};
    char b[BUF];
    f(a, 0, M, b);
    return 0;
}

思路:

其实最喜欢这样的填空题了,因为很容易判断填的是对还是错=.=这个题其实当时都没怎么思考,直接就填了个f(a, k+1, m-i, b)试一试,打出来的结果跟题目中的一致,还自己加个统计变量,刚好是101行。。。大概就是要搞出几个变量的含义吧,f(a, k, m, b)里面,a限制了每一次往答案数组b填的相同字母个数,k是表示第几个字母,m表示剩余的字母个数,b是答案数组。f函数里面的i就是表示第k个字母选i个往b里放,而j就是放的过程,放完以后自然就要考虑下一个了。所以答案就是f(a, k+1, m-i, b),表示放下一个字母,这一次放了i个,所以还剩下m-i个可以放。

方格填数

填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)

   +--+--+--+
   |  |  |  |
+--+--+--+--+
|  |  |  |  |
+--+--+--+--+
|  |  |  |
+--+--+--+

一共有多少种可能的填数方案?

请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

思路

用全排列生成所有情况,然后判断是否合法就行了。另外题目中给的条件:“连续的两个数字不能相邻” 等价于 “相邻的数字不连续”,这个是至关重要的。答案是1580。

#include <bits/stdc++.h>
using namespace std;
int ans;
int a[10];
bool chk(int i, int j) {
    return abs(a[i] - a[j]) != 1;
}
bool chk() {
    return chk(0, 1) && chk(1, 2) && chk(3, 4) && chk(4, 5) && chk(5, 6) && chk(7, 8) && chk(8, 9) &&
    chk(0, 4) && chk(1, 5) && chk(2, 6) && chk(3, 7) && chk(4, 8) && chk(5, 9) &&
    chk(0, 5) && chk(1, 6) && chk(3, 8) && chk(4, 9) &&
    chk(0, 3) && chk(1, 4) && chk(2, 5) && chk(4, 7) && chk(5, 8) && chk(6, 9);
}
int main() {
    for (int i = 0; i < 10; i++) a[i] = i;
    do {
        ans += chk();
    } while (next_permutation(a, a + 10));
    cout << ans << endl;
    return 0;
}

剪邮票

+--+--+--+--+
|  |  |  |  |
+--+--+--+--+
|  |  |  |  |
+--+--+--+--+
|  |  |  |  |
+--+--+--+--+

有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)

请你计算,一共有多少种不同的剪取方法。

请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

思路

先生成所有邮票的形状,然后看这个形状在一个连通块内,对每个格子编号,那么它的形状可以用5个数来表示,判断是否为联通块用dfs或bfs都行。答案是116。

#include <bits/stdc++.h>
using namespace std;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int a[5], ans;
bool flag[3][4], vis[3][4];
void dfs2(int x, int y) {
    vis[x][y] = 1;
    for (int i = 0; i < 4; i++) {
        int xx = x + dx[i];
        int yy = y + dy[i];
        if (xx >= 0 && xx < 3 && yy >= 0 && yy < 4 && !flag[xx][yy] && !vis[xx][yy]) dfs2(xx, yy);
    }
}
bool work() {
    memset(flag, 1, sizeof(flag));
    memset(vis, 0, sizeof(vis));
    for (int i = 0; i < 5; i++) flag[a[i] / 4][a[i] % 4] = 0;
    dfs2(a[0] / 4, a[0] % 4);
    for (int i = 0; i < 5; i++) {
        if (!vis[a[i] / 4][a[i] % 4]) return 0;
    }
    return 1;
}
void dfs(int k, int b) {
    if (k == 5) {
        ans += work();
        return;
    }
    for (int i = b; i <= 7 + k; i++) {
        a[k] = i;
        dfs(k + 1, i + 1);
    }
}

int main() {
    dfs(0, 0);
    cout << ans << endl;
    return 0;
}

四平方和

四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多4个正整数的平方和。
如果把0包括进去,就正好可以表示为4个数的平方和。

比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思)

对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对4个数排序:
0 <= a <= b <= c <= d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法

程序输入为一个正整数N (N<5000000)
要求输出4个非负整数,按从小到大排序,中间用空格分开

例如,输入:
5
则程序应该输出:
0 0 1 2

再例如,输入:
12
则程序应该输出:
0 2 2 2

再例如,输入:
773535
则程序应该输出:
1 1 267 838

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 3000ms

思路

最简单的思路4重for循环,不知道这样能过多少组数据,毕竟这个复杂度不好确定,虽然最坏是O(n2)的。一个简单的优化办法是预处理所有两个数的平方和,放在一个表中,要求根据平方和要迅速得到它的所有解。所以可以用一个map<int,vector<Node>>来搞。具体见代码。

#include <bits/stdc++.h>
using namespace std;
#include "local.h"
struct Node {
    int x, y;
    Node() {}
    Node(int x, int y) {
        this->x = x;
        this->y = y;
    }
};
map<int, vector<Node> > mp;
int n;
void work() {
    for (int i = 0; i * i <= n; i++) {
        for (int j = i; i * i + j * j <= n; j++) {
            vector<Node>::iterator it, st = mp[n - i * i - j * j].begin(), ed = mp[n - i * i - j * j].end();
            for (it = st; it != ed; it++) {
                if (j <= it->x) {
                    cout << i << " " << j << " " << it->x << " " << it->y << endl;
                    return;
                }
            }
        }
    }
}
int main() {
    cin >> n;
    for (int i = 0; i * i <= n; i++) {
        for (int j = i; i * i + j * j <= n; j++) {
            mp[i * i + j * j].push_back(Node(i, j));
        }
    }
    work();
    return 0;
}

交换瓶子

有N个瓶子,编号 1 ~ N,放在架子上。

比如有5个瓶子:
2 1 3 5 4

要求每次拿起2个瓶子,交换它们的位置。
经过若干次后,使得瓶子的序号为:
1 2 3 4 5

对于这么简单的情况,显然,至少需要交换2次就可以复位。

如果瓶子更多呢?你可以通过编程来解决。

输入格式为两行:
第一行: 一个正整数N(N<10000), 表示瓶子的数目
第二行:N个正整数,用空格分开,表示瓶子目前的排列情况。

输出数据为一行一个正整数,表示至少交换多少次,才能完成排序。

例如,输入:
5
3 1 2 5 4

程序应该输出:
3

再例如,输入:
5
5 4 3 2 1

程序应该输出:
2

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

思路

每一种该序列都可以看成一种置换,每一种置换都可以写成若干循环的乘积,比如
2 1 3 5 4
1 2 3 4 5
可以写成(1 2)(3)(4 5),显然我们的目标使使得循环个数最多即(1)(2)(3)(4)(5)
在循环内部交换,除最后一次外,每次可以让一个到达目标位置,即循环个数增加1。在循环外部交换,会较少循环的个数,得不偿失。因此得到结论,先把序列写成若干循环的乘积,然后再循环内部交换,交换的次数为循环的大小-1,最后结果等于n-循环的个数

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 7;
int a[N], ans, n;
bool vis[N];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
    }
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            ans++;
            vis[i] = 1;
            for (int j = a[i]; j != i; j = a[j]) vis[j] = 1;
        }
    }
    cout << n - ans << endl;
    return 0;
}

最大比例

X星球的某个大奖赛设了M级奖励。每个级别的奖金是一个正整数。
并且,相邻的两个级别间的比例是个固定值。
也就是说:所有级别的奖金数构成了一个等比数列。比如:
16,24,36,54
其等比值为:3/2

现在,我们随机调查了一些获奖者的奖金数。
请你据此推算可能的最大的等比值。

输入格式:
第一行为数字N,表示接下的一行包含N个正整数
第二行N个正整数Xi(Xi<1 000 000 000 000),用空格分开。每个整数表示调查到的某人的奖金数额

要求输出:
一个形如A/B的分数,要求A、B互质。表示可能的最大比例系数

测试数据保证了输入格式正确,并且最大比例是存在的。

例如,输入:
3
1250 200 32

程序应该输出:
25/4

再例如,输入:
4
3125 32 32 200

程序应该输出:
5/2

再例如,输入:
3
549755813888 524288 2

程序应该输出:
4/1

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 3000ms

思路

首先将原序列从小到大排序去重,然后得到最大可能的比值a[1]/a[0],因为如果比这个大,显然第1、2项就不能满足。而且其它的可能的比值只能是a[1]/a[0]的平方根、立方根等等(如果它们是有理数的话)。那么一个暴力的思路就出来了,枚举所有a[0]和a[1]之间”隐藏”的项数,比如为1,那么就应该把比例确定为(a[1]/a[0])1/2(如果为2,就是(a[1]/a[0])1/3),然后依次判断后面的比例是否可以通过这个比例累乘得到。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[100], A, B, g, x, y, s, t;
int n, m;
ll gcd(ll a, ll b) {
    return b? gcd(b, a % b) : a;
}
ll power(ll a, int b) {
    ll ans = 1;
    for (int i = 0; i < b; i++) ans *= a;
    return ans;
}
ll chk(ll A, int d) {
    double r = pow(A, 1.0 / d);
    ll ir = (ll)(r + 0.5);
    if (power(ir, d) == A) return ir;
    return 0;
}
ll calc(ll A, ll x) {
    if (x == 1) return 0;
    ll cnt = 0;
    while (A >= x) {
        A /= x;
        cnt++;
    }
    if (A == 1) return cnt;
    return 0;
}
bool work(ll x, ll y) {
    ll A, B;
    for (int i = 1; i < m - 1; i++) {
        A = a[i + 1];
        B = a[i];
        g = gcd(A, B);
        A /= g;
        B /= g;
        s = calc(A, x);
        t = calc(B, y);
        if (y == 1) t = s;
        if (s != t || !s) return false;
    }
    cout << x << "/" << y << endl;
    return true;
}
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    sort(a, a + n);
    m = unique(a, a + n) - a;
    if (m == 1) {
        puts("1/1");
        return 0;
    }
    A = a[1];
    B = a[0];
    g = gcd(A, B);
    A /= g;
    B /= g;
    for (int i = 1; power(2, i) <= A; i++) {
        x = chk(A, i);
        y = chk(B, i);
        if (B == 1) y = 1;
        if (x && y) {
            if (work(x, y)) break;
        }
    }
    return 0;
}

以上是关于第七届蓝桥杯本科B组省赛的主要内容,如果未能解决你的问题,请参考以下文章

第七届蓝桥杯(2016年)JavaB组省赛真题解析

第七届蓝桥杯(2016年)JavaA组省赛真题解析

第七届蓝桥杯大赛个人赛省赛(软件类)B组

算法笔记_123:蓝桥杯第七届省赛(Java语言B组部分习题)试题解答

第七届蓝桥杯java b组难吗

2020第十一届蓝桥杯真题JAVA B组省赛第二场答案分享(2020.10.17)