Bootstrap

第七届蓝桥杯C\C++省赛大学B组

A题:煤球数量

有一堆煤球,堆成三角棱锥形。具体: 第一层放1个,
第二层3个(排列成三角形), 第三层6个(排列成三角形), 第四层10个(排列成三角形), …
如果一共有100层,共有多少个煤球?
请填表示煤球总数目的数字。

【Solution】
暴力枚举。

【Code】

#include<iostream>
using namespace std;

const int N = 100;

int main(){
    int res = 0;
    int t = 0;
    for(int i = 1; i <= N; ++i){
        t += i;
        res += t;
    }
    cout << res << endl; // 171700
    return 0;
}

B题:凑算式

在这里插入图片描述
这个算式中 A ~ I 代表 1 ~ 9 的数字,不同的字母代表不同的数字。
比如:
6 + 8 / 3 + 952 / 714 就是一种解法,
5 + 3 / 1 + 972 / 486 是另一种解法。
这个算式一共有多少种解法?

【Solution】
暴力枚举。
10 的 9 的次方,一到两秒可以计算完。

【Notice】
判断不能相同的时候,不能写在for语句退出的判断中,这样会导致后面数据遍历不到。

【Code】

#include<iostream>
using namespace std;

const int N = 100;

bool judge(int a, int b, int c, int d, int e, int f, int g, int h, int i){
    int num1 = (g * 100 + h * 10 + i) * c * a;
    int num2 = (b * (g * 100 + h * 10 + i));
    int num3 = (d * 100 + e * 10 + f) * c;
    int num4 = (g * 100 + h * 10 + i) * c;
    return num1 + num2 + num3 == num4 * 10;
}

int main(){
    int ans = 0;
    // 注意:不等于的判断不能写在for的判断里面,要写在外面。
    for(int a = 1; a <= 9; a ++)
    for(int b = 1; b <= 9; b ++)
    for(int c = 1; c <= 9; c ++)
    for(int d = 1; d <= 9; d ++)
    for(int e = 1; e <= 9; e ++)
    for(int f = 1; f <= 9; f ++)
    for(int g = 1; g <= 9; g ++)
    for(int h = 1; h <= 9; h ++)
    for(int i = 1; i <= 9; i ++)
    if(a != b && a != c && a != d && a != e && a != f && a != g && a != h && a != i
              && b != c && b != d && b != e && b != f && b != g && b != h && b != i
                        && c != d && c != e && c != f && c != g && c != h && c != i
                                  && d != e && d != f && d != g && d != h && d != i
                                            && e != f && e != g && e != h && e != i
                                                      && f != g && f != h && f != i
                                                                && g != h && g != i
                                                                          && h != i
       && judge(a, b, c, d, e, f, g, h, i)) ans ++;
    cout << ans << endl; // 29
    return 0;
}

C题:生日蜡烛

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

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

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

请填写他开始过生日party的年龄数。

【Solution】
暴力枚举

【CODE】

#include<iostream>
using namespace std;

int main(){
    int res = 0;
    for(int i = 1; i < 100; ++i){
        res = 0;
        for(int j = i; j < 100; ++j){
            res += j;
            if(res == 236) cout << i << endl; // 26
        } 
    }
    return 0;
}

D题:快速排序

排序在各种场合经常被用到。

快速排序是十分常用的高效率的算法。
其思想是:先选一个“标尺”,

用它把整个队列过一遍筛子,

以保证:其左边的元素都不大于它,其右边的元素都不小于它。
这样,排序问题就被分割为两个子区间。

再分别对子区间排序就可以了。
下面的代码是一种实现,请分析并填写划线部分缺少的代码。

#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;
}

【Code】
a[j] = x; or swap(a, p, j);

E题:抽签(递归)

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

// 人数数组,k表示国别的种类,m表示剩余的名额,b表示答案数组
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;
}

【Code】
f(a, k + 1, m - i, b);

F题:方格填数(全排列)

如下的10个格子
在这里插入图片描述

填入 0 ~ 9 的数字。要求:连续的两个数字不能相邻(左右、上下、对角都算相邻)。
一共有多少种可能的填数方案?

【解题思路】
在这里插入图片描述
0~9全排列(递归),按条件判断。

【Code】

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;

int res;
vector<int> a;
bool st[12];

void dfs(int s){
    if(s == 10){
        if(!(
               abs(a[0] - a[1]) == 1 ||
               abs(a[0] - a[4]) == 1 ||
               abs(a[0] - a[5]) == 1 ||
               abs(a[0] - a[3]) == 1 ||
               abs(a[1] - a[2]) == 1 ||
               abs(a[1] - a[6]) == 1 ||
               abs(a[1] - a[4]) == 1 ||
               abs(a[1] - a[5]) == 1 ||
               abs(a[2] - a[6]) == 1 ||
               abs(a[2] - a[5]) == 1 ||
               abs(a[3] - a[4]) == 1 ||
               abs(a[3] - a[8]) == 1 ||
               abs(a[3] - a[7]) == 1 ||
               abs(a[4] - a[5]) == 1 ||
               abs(a[4] - a[9]) == 1 ||
               abs(a[4] - a[8]) == 1 ||
               abs(a[4] - a[7]) == 1 ||
               abs(a[5] - a[6]) == 1 ||
               abs(a[5] - a[9]) == 1 ||
               abs(a[5] - a[8]) == 1 ||
               abs(a[6] - a[9]) == 1 ||
               abs(a[7] - a[8]) == 1 ||
               abs(a[8] - a[9]) == 1 )) res ++;
               return;
        }

    for(int i = 0; i < 10; ++i)
        if(!st[i]){
            st[i] = true;
            a.push_back(i);
            dfs(s + 1);
            a.pop_back();
            st[i] = false;
        }
}

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

// 使用next_permutation函数算全排列
#include<algorithm>
#include<iostream>

using namespace std;

int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

int main(){
        int res = 0;
        while(next_permutation(a, a + 10)){
            if(!(
               abs(a[0] - a[1]) == 1 || 
               abs(a[0] - a[4]) == 1 ||
               abs(a[0] - a[5]) == 1 || 
               abs(a[0] - a[3]) == 1 || 
               abs(a[1] - a[2]) == 1 ||
               abs(a[1] - a[6]) == 1 || 
               abs(a[1] - a[4]) == 1 || 
               abs(a[1] - a[5]) == 1 ||
               abs(a[2] - a[6]) == 1 || 
               abs(a[2] - a[5]) == 1 ||
               abs(a[3] - a[4]) == 1 ||
               abs(a[3] - a[8]) == 1 ||
               abs(a[3] - a[7]) == 1 ||
               abs(a[4] - a[5]) == 1 ||
               abs(a[4] - a[9]) == 1 ||
               abs(a[4] - a[8]) == 1 ||
               abs(a[4] - a[7]) == 1 ||
               abs(a[5] - a[6]) == 1 ||
               abs(a[5] - a[9]) == 1 ||
               abs(a[5] - a[8]) == 1 ||
               abs(a[6] - a[9]) == 1 ||
               abs(a[7] - a[8]) == 1 ||
               abs(a[8] - a[9]) == 1 )) res ++;
        }
        cout << res << endl; // 1580
        return 0;
}

G题:剪邮票(dfs)

问题描述
如图,有 12 张连在一起的 12 生肖的邮票。
在这里插入图片描述
现在你要从中剪下 5 张来,要求必须是连着的(仅仅连接一个角不算相连)

比如下图中,粉红色所示部分就是合格的剪取。
在这里插入图片描述

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

【Solution】
枚举所有5个元素的情况,通过dfs判断是否相连。

【Notice】
巧妙的边界处理:去除 5 和 10 这两个数当边界

【CODE】

#include<iostream>
#include<cstring>
using namespace std;

// 去除了5和10,有利于判断右端的边界
int num[] = {1, 2, 3, 4, 6, 7, 8, 9 ,11, 12, 13, 14}; // 避免边界问题
int dir[] = {-1, 1, -5, 5}; // 上下左右的遍历
int s[5]; // 保存数据
bool st[5]; // 标记数据

void dfs(int n){
//    cout << "dfs" << endl;
    for(int i = 0; i < 4; ++i){
        int t = s[n] + dir[i];
        if(t < 1 || t > 14 || t == 5 || t == 10) continue;

        for(int j = 0; j < 5; ++j)
            if(t == s[j] && !st[j]){ // 判断是否为杯选取的数
                st[j] = true;
                dfs(j);
            }
     }
}

int main(){
    int ans = 0;
    // 遍历数组的组合情况
    for(int a = 0; a < 12; ++a)
        for(int b = a + 1; b < 12; ++b)
            for(int c = b + 1; c < 12; ++c)
                for(int d = c + 1; d < 12; ++d)
                    for(int e = d + 1; e < 12; ++e){
                        s[0] = num[a];
                        s[1] = num[b];
                        s[2] = num[c];
                        s[3] = num[d];
                        s[4] = num[e];
//                        cout << "123" << endl;

                        memset(st, false, sizeof st);

                        st[0] = true;
                        dfs(0);

                        bool flag = true;
                        for(int i = 0; i < 5; ++i){ // 检查是否五个数相邻
                            if(!st[i]) flag = false;
                        }

                        if(flag) ans ++;

                    }

//    cout << "123" << endl;
    cout << ans <<endl; // 116
    return 0;
}

H题:四平方和(化繁为简)

四平方和定理,又称为拉格朗日定理:

每个正整数都可以表示为至多44 个正整数的平方和。

如果把 0 包括进去,就正好可以表示为 4 个数的平方和。

比如:

5=02+02+12+22
7=12+12+12+22

对于一个给定的正整数,可能存在多种平方和的表示法。

要求你对 4 个数排序:

0≤a≤b≤c≤d

并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法。

输入格式

输入一个正整数 N。

输出格式

输出4个非负整数,按从小到大排序,中间用空格分开。

数据范围

0<N<5*106

输入样例:
5
输出样例:
0 0 1 2

【Solution】
四重循环优化三重循环。

【Code】

// 三重暴力O(n * n * n)
#include<iostream>
#include<cmath>
using namespace std;

const int N = 5 * 1e6 + 10;
int n;

int main(){
    cin >> n;
    int sqr_n = sqrt(n);
    for(int a = 0; a <= sqr_n; ++a)
        for(int b = a; b <= sqr_n; ++b)
            for(int c = b; c <= sqr_n; ++c){
                
                int d = sqrt(n - a * a - b * b - c * c);
                
                if(n == a * a + b * b + c * c + d * d){
                    if(c > d) swap(c, d);
                    cout << a << " " << b << " " << c << " " << d;
                    return 0;
                }
            }
}

【Solution2】
先将后两个数的平方和存入哈希表中;再遍历前两个数据,并判断四者和是否符合即可。

【Code2】

// O(n * n)
#include<iostream>
#include<cmath>
using namespace std;

const int N = 5 * 1e6 + 10;

int h[N];

int main(){
    int n;
    cin >> n;
    for(int i = 0; i * i <= n / 2; ++i) // 除二是小于d做准备
        for(int j = i; j * j + i * i <= n; ++j)
            if(!h[i * i + j * j]) h[i * i + j * j] = i + 1; // 加1 是为了下面判断做的
        
    for(int i = 0; i * i * 4 <= n; ++i)
        for(int j = i; i * i + j * j <= n / 2; ++j)
            if(h[n - i * i - j * j]){ // 这里判断会出现 0,上面加1用再这里
                int c = h[n - i * i - j * j] - 1;
                int d = int(sqrt(n - i * i - c * c - j * j) * 1.0);
                cout << i <<' ' << j <<' ' << c << " " << d;
                return 0;
            }
}

I题:交换瓶子(找规律)

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

比如有 5 个瓶子:

2 1 3 5 4

要求每次拿起 2 个瓶子,交换它们的位置。

经过若干次后,使得瓶子的序号为:

1 2 3 4 5

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

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

输入格式

第一行包含一个整数 N,表示瓶子数量。

第二行包含 N 个整数,表示瓶子目前的排列状况。

输出格式

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

数据范围

1≤N≤10000,

输入样例1:
5
3 1 2 5 4
输出样例1:
3
输入样例2:
5
5 4 3 2 1
输出样例2:
2

【Solution】

第一重找到对应不相等的位置,接着再找和其位置相等的数据交换即可。

【CODE】O(n * n)

// 暴力遍历
#include<iostream>
#include<cstring>
using namespace std;

const int N = 10010;
int n;
int a[N];

int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    
    int ans = 0;
    for(int i = 1; i <= n; ++i)
        if(a[i] != i) // 找对应的位置,并替换.
            for(int j = i + 1; j <= n; ++j)
                if(a[j] == i){
                    swap(a[i], a[j]);
                    ans ++;
                    break;
                }
                
    cout << ans << endl;
    return 0;
}

【Solution2】
我们将所有位置重叠的数据记为一个集体。
在这里插入图片描述
【Code2】

// 优化算法, 时间复杂度O(n)
// 图解https://www.acwing.com/solution/content/7917/;晚上写
#include<iostream>
using namespace std;

const int N = 10010;
int n;
int a[N];
bool st[N];

int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    
    int cnt = 0;
    
    for(int i = 1; i <= n; ++i)
        if(!st[i]){
            cnt ++;
            for(int j = i; !st[j] ; j = a[j]) st[j] = true;
        }
    
    cout << n - cnt << endl;
}

J题:最大比例(数学知识)(还不是太理解)

X星球的某个大奖赛设了 M 级奖励。

每个级别的奖金是一个正整数。

并且,相邻的两个级别间的比例是个固定值。

也就是说:所有级别的奖金数构成了一个等比数列。

比如:16,24,36,54,其等比值为:3/2。

现在,我们随机调查了一些获奖者的奖金数。

请你据此推算可能的最大的等比值。

输入格式

第一行为数字 N ,表示接下的一行包含 N 个正整数。

第二行 N 个正整数 Xi,用空格分开,每个整数表示调查到的某人的奖金数额。

输出格式

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

数据范围

0<N<100
0<Xi<1012
数据保证一定有解。

输入样例1:
3
1250 200 32
输出样例1:
25/4
输入样例2:
4
3125 32 32 200
输出样例2:
5/2
输入样例3:
3
549755813888 524288 2
输出样例3:
4/1

【Solution】
先将数据排序,并计算于以a[0]为分母,a[i]为分子的最简式;分别保存在 b[] 和 c[] 中。因为要保存分子分母,使其达到最简,所以使用辗转相除法直接计算除最大公约数。

接下来对每个分子,分母互相计算其最大公约数(化简的),就使用更相减损法。
【Code】

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N = 110;

int n;
// a存储数据,b存储分子,c存储分母
LL a[N], b[N], c[N];

LL gcd(LL a, LL b){
    return b ? gcd(b, a % b) : a;
}

LL gcd_sub(LL a, LL b){
    if(a < b) swap(a, b);
    return b == 1 ? a : gcd_sub(b, a / b);
}

int main(){
    cin >> n;
    for(int i = 0; i < n; ++i){
        cin >> a[i];
    }
    
    sort(a, a + n);
    int cnt = 0;
    for(int i = 1; i < n; ++i){
        if(a[i] != a[i - 1]){
        	// 这里是用辗转相除法计算最大公约数
        	// 便于 后面的要除到达最简。
            LL t = gcd(a[i], a[0]);
            b[cnt] = a[i] / t;
            c[cnt] = a[0] / t;
            // 所有分数都是公比的k次方
            cnt ++;
        }
    }
    
    LL up = b[0], down = c[0];
    
    for(int i = 1; i < cnt; ++i){
        // 这里使用更相减损法,获得最大的公约数并且化简得
        up = gcd_sub(up, b[i]);
        down = gcd_sub(down, c[i]);
    }
    
    cout << up << "/" << down << endl;
    return 0;
}

参考文章

交换瓶子

;