2021年第十二届蓝桥杯国赛C++B组题解

试题 A: 带宽

【问题描述】

小蓝家的网络带宽是 200 Mbps,请问,使用小蓝家的网络理论上每秒钟最多可以从网上下载多少 MB 的内容。

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【问题分析】

计算机常识题, Mbps表示Million bits per second,表示每秒能传输多少Mb的数据,所以带宽为200Mbps每秒最多可以传输200Mb数据,而8Mb=1MB,所以答案为 25MB


试题 B: 纯质数

【问题描述】

如果一个正整数只有 1 和它本身两个约数,则称为一个质数(又称素数)。
前几个质数是:2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, · · · 。
如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如:2, 3, 5, 7, 23, 37 都是纯质数,而 11, 13, 17, 19, 29, 31 不是纯质数。
当然 1, 4, 35 也不是纯质数。 请问,在 1 到 20210605 中,有多少个纯质数?

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【问题分析】

先将1到20210605中所有的质数筛选出来,使用is_prime数组标记是否为质数。
遍历所有质数(可在筛选质数的同时遍历),判断该质数每个数位是否为质数。

【解题代码】

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 20210610;
bool is_prime[N];

int main() {
    int n = 20210605;
    
    is_prime[0] = is_prime[1] = 1;
    int cnt = 0; 
    for(int i = 2;i <= n;i ++) {
        if(!is_prime[i]) {
            for(int j = i + i;j <= n;j += i) {
                is_prime[j] = 1;
            }

            int x = i;
            cnt ++;

            while(x) {
                int t = x % 10;
                x /= 10;
                
                if(is_prime[t]) {
                    cnt --;
                    break;
                }
            }
        }
    }

    printf("%d", cnt); // 1903

    return 0;
}

试题 C: 完全日期

【问题描述】

如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日期。
例如:2021 年 6 月 5 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 5 = 16,而 16 是一个完全平方数,它是 4 的平方。所以 2021 年 6 月 5 日是一个完全日期。
例如:2021 年 6 月 23 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16,是一个完全平方数。所以 2021 年 6 月 23 日也是一个完全日期。
请问,从 2001 年 1 月 1 日到 2021 年 12 月 31 日中,一共有多少个完全日期?

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【问题分析】

完全平方数指的是能表示成某个整数的平方的数,因为日期位数为8位,八位数字之和小于9的平方,所以我们先算出从1到9各数的平方值存入is_square数组,表示一个数是否为完全平方数。
遍历20010101到20211231间的每个数,验证其作为日期的合法性,如合法就将日期的每个数位进行想加,判断和是否为完全平方数。

【解题代码】

#include <iostream>
#include <cstdio>

using namespace std;

int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool is_square[100];

bool is_leap(int x) {
    return (x % 4 == 0 && x % 100 == 0) || x % 400 == 0;
}

bool check(int x) {
    int y = x / 10000, m = x % 10000 / 100, d = x % 100;
    
    days[2] = 28 + is_leap(y);
    
    if(m > 12 || m < 1) return 0;
    if(d > days[m] || d < 1) return 0;
    
    return 1;
}

int main() {
    int cnt = 0;
    
    for(int i = 1;i <= 9;i ++) {
        is_square[i * i] = 1;
    }
    
    for(int i = 20010101;i <= 20211231;i ++) {
        if(check(i)) {
            int x = i;
            
            int sum = 0;
            while(x) {
                int t = x % 10;
                x /= 10;
                
                sum += t;
            }
            
            if(is_square[sum]) cnt ++;
        }
    }
    
    printf("%d", cnt); // 977
    
    return 0;
}

试题 D: 最小权值

【问题描述】

对于一棵有根二叉树 T,小蓝定义这棵树中结点的权值 W(T) 如下:
空子树的权值为 0。
如果一个结点 v 有左子树 L, 右子树 R,分别有 C(L) 和 C® 个结点,则 W(v) = 1 + 2W(L) + 3W® + (C(L)) 2 C®。
树的权值定义为树的根结点的权值。
小蓝想知道,对于一棵有 2021 个结点的二叉树,树的权值最小可能是多少?

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【问题分析】

【解题代码】

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 2030;
long long w[N];

int main() {
	memset(w, 0x3f, sizeof w);
	
	w[0] = 0;
	for(int i = 1;i <= 2021;i ++) {
		for(int l = 0;l <= i - 1;l ++) {
			w[i] = min(w[i], 1 + 2 * w[l] + 3 * w[i - 1 - l] + l * l * (i - 1 - l));
		}
	}
	
	printf("%lld", w[2021]);
	
	return 0;
}

试题 E: 大写

【问题描述】

给定一个只包含大写字母和小写字母的字符串,请将其中所有的小写字母 转换成大写字母后将字符串输出。

【输入格式】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【输出格式】

输出转换成大写后的字符串。

【样例】

【样例输入】 LanQiao
【样例输出】 LANQIAO

【问题分析】

遍历字符串中的每个字符做判断

【解题代码】

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
	string a;
	cin >> a;
	
	int t = 'A' - 'a';
	for(int i = 0;i < a.size();i ++) {
		if(a[i] >= 'a' && a[i] <= 'z') a[i] += t;
	}
	
	cout << a;
	
	return 0;
}

试题 F: 123

【问题描述】

小蓝发现了一个有趣的数列,这个数列的前几项如下:
1, 1, 2, 1, 2, 3, 1, 2, 3, 4, …
小蓝发现,这个数列前 1 项是整数 1,接下来 2 项是整数 1 至 2,接下来 3 项是整数 1 至 3,接下来 4 项是整数 1 至 4,依次类推。
小蓝想知道,这个数列中,连续一段的和是多少。

【输入格式】

输入的第一行包含一个整数 T,表示询问的个数。
接下来 T 行,每行包含一组询问,其中第 i 行包含两个整数 l i l_i li r i r_i ri,表示询问数列中第 l i l_i li 个数到第 r i r_i ri 个数的和。

【输出格式】

输出 T 行,每行包含一个整数表示对应询问的答案。

【样例】

【样例输入】
3
1 1
1 3
5 8
【样例输出】
1
4
8

【问题分析】

1、因为题目的要求为“求出数列中第 l i l_i li 个数到第 r i r_i ri 个数的和”,不难想到可以使用前缀和来求解,但是本题数据范围很大单独靠前缀和显然无法解决,故因想办法降低算法复杂度;

2、通过观察不难发现,题目中给出的数列中的每个数可以描述为第n部分的第m个数
其中第N部分指的是数列中的这一部分的数为1至n,由此还可以得出第n部分的总和为 n ( n + 1 ) / 2 n(n + 1) / 2 n(n+1)/2
3、因为第 i 个数可以表示数列中的第n部分的第m个数,所以数列前 i 项的和可以表示为数列前n-1部分的和 + 数字1到m的和(也就等于数列第m部分的和)
4、定义数组a[], s[],使用数组a[n]表示数列第n部分的和,s[n]表示数列前n部分的和

【解题代码】

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 1414220; 
// 因为l,r <= 1e12,数列各部分数字的数量为首项和公差都为1的等差序列
// 所以N应满足:N(N+1)/2 > 1e12
long long s[N], a[N];

long long cal(long long x) {
	// 使用2分加快寻找x所处部分的前一部分是哪个部分
	int l = 0, r = N;
	
	while(l < r) {
		int mid = (l + r + 1) / 2;
		
		// 数列各部分数字的数量为首项和公差都为1的等差序列
		// 所以前n部分数字的数量和就为n(n+1)/2,也就是a[n]
		if(a[mid] > x) r = mid - 1;
		else l = mid;
	}
	
	return s[l] + a[x - a[l]];
}

int main() {
	int t;
	scanf("%d", &t);
	
	for(int i = 1;i < N;i ++) {
		a[i] = a[i - 1] + i;
		s[i] = s[i - 1] + a[i];
	}
	
	while(t --) {
		long long l, r;
		scanf("%lld %lld", &l, &r);
		
		printf("%lld\n", cal(r) - cal(l - 1));
	}
	
	return 0;
}

试题 G: 异或变换

【问题描述】

小蓝有一个 01 串 s = s 1 s 2 s 3 ⋅ ⋅ ⋅ s n s = s_1 s_2 s_3 · · · s_n s=s1s2s3⋅⋅⋅sn
以后每个时刻,小蓝要对这个 01 串进行一次变换。每次变换的规则相同。
对于 01 串 s = s 1 s 2 s 3 ⋅ ⋅ ⋅ s n s = s_1 s_2 s_3 · · · s_n s=s1s2s3⋅⋅⋅sn,变换后的 01 串 s ′ = s 1 ′ s 2 ′ s 3 ′ ⋅ ⋅ ⋅ s n ′ s_′=s^′_1 s^′_2 s^′_3· · · s^′_n s=s1s2s3⋅⋅⋅sn 为:
s 1 ′ = s 1 s^′_1 = s_1 s1=s1;
s i ′ = s i − 1 ⊕ s i s^′_i = s_{i−1} ⊕ s_i si=si1si
其中 a ⊕ b 表示两个二进制的异或,当 a 和 b 相同时结果为 0,当 a 和 b不同时结果为 1。
请问,经过 t 次变换后的 01 串是什么?

【输入格式】

输入的第一行包含两个整数 n, t,分别表示 01 串的长度和变换的次数。
第二行包含一个长度为 n 的 01 串。

【输出格式】

输出一行包含一个 01 串,为变换后的串。

【样例】

【样例输入】
5 3
10110
【样例输出】
11010

【问题分析】

按题目要求进行的异或变换,存在周期性:
例如:样例中 s = 10110 , s 1 = 11101 , s 2 = 10011 , s 3 = 11010 , s 4 = 10111 , s 5 = 11100 , s 6 = 10010 , s 7 = 11011 , s 8 = 10110 = s s = 10110, s^1 = 11101, s^2 = 10011, s^3 = 11010, s^4 = 10111, s^5 = 11100, s^6 = 10010, s^7 = 11011, s^8 = 10110 = s s=10110,s1=11101,s2=10011,s3=11010,s4=10111,s5=11100,s6=10010,s7=11011,s8=10110=s可得对于二进制串10110,它的异或变换周期为8。
对于不同01串周期的大小的规律,通过打表模拟得以下数据

01串长度异或变换周期
11
22
34
44
58
88
916

不难发现,对于长度为n的01二进制串,其异或变换的周期是大于等于n的最小2次幂
故在解题过程中只需模拟给出字符串在一个变换周期中的所有情况,即可得出最终答案。

【解题代码】

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 16385; // 当长度n取10000时,周期长度应为16384
string str[N];

int main() {
    int n, cnt = 0;
    long long t;
    cin >> n >> t >> str[0];
    
    cnt ++;
    while(1) {
        string t = str[cnt - 1];
        string tstr = "";
        
        tstr += t[0];
        for(int i = 1;i < t.size();i ++) {
            if(t[i] == t[i-1]) tstr += '0';
            else tstr += '1';
        }
        
        if(tstr == str[0]) break;
        
        str[cnt ++] = tstr;
    }
    
    cout << str[t % cnt];
    
    return 0;
}

试题 H: 二进制问题

【问题描述】

小蓝最近在学习二进制。他想知道 1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。你能帮助他吗?

【输入格式】

输入一行包含两个整数 N 和 K。

【输出格式】

输出一个整数表示答案。

【样例】

【样例输入】 7 2
【样例输出】 3

【问题分析】

假设整数N表示为二进制数后有x位,则该问题可以转化为:向x位二进制数中,选择k位等于1,且最终得出的二进制数小于N
从第x位开始逆序处理:如果第x位不选择1,那么将有 C x − 1 k C^k_{x-1} Cx1k种选择,且每个选择产生的数都小于N;如果整数N的第x位为1,则第x位可以选择1(如果不为1而选择1的话,不管剩下的位数如何选择组成的数都会大于N),那么剩下的问题就变成了在剩下x-1位二进制数中选k-1位等于1,且最终得出的二进制数小于N;
因为处理过程中会多次用到组合数,所以可以事先将可能用到的所有组合数使用 C b a = C b − 1 a − 1 + C b − 1 a C^a_b = C^{a-1}_{b-1} +C^a_{b-1} Cba=Cb1a1+Cb1a算出

【解题代码】

#include <algorithm>
#include <iostream>
#include <cstdio>

using namespace std;

const int N = 65;
long long c[N][N], res;
int num[N], idx ;

int main()
{
	long long n, k;
	scanf("%lld %lld", &n, &k);
	
	// 先算出所有可能用到的组合数的值存入数组C
	// C[i][j] 表示在i个位置中选出j个位置的情况
	for(int i = 0;i < N;i ++) { 
		for(int j = 0;j <= i;j ++) {
			if(!j) c[i][j] = 1;
			else c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
		}
	}
	
	while(n) { // 将整数n的每个二进制数取出
		num[idx ++] = n & 1;
		n >>= 1;
	}
	
	for(int i = idx - 1;i >= 0;i --) { // 从最后一位二进制数开始处理
		// 只有这位二进制数等于1才做处理
		// 如果不为1那么选这位数的所有情况一定已经在之前为1的二进制位的处理中处理过了
		if(num[i]) { 
			res += c[i][k]; // 不选1的情况
			
			k --;
			if(k == 0) {
				res ++;
				break;
			} 
		}
	}
	
	printf("%lld", res);
		
  	return 0;
}

试题 I: 翻转括号序列

【问题描述】

给定一个长度为 n 的括号序列,要求支持两种操作:
1.将 [ L i L_i Li, R i R_i Ri] 区间内(序列中的第 L i L_i Li个字符到第 R i R_i Ri个字符)的括号全部翻转(左括号变成右括号,右括号变成左括号)。
2.求出以 L i L_i Li为左端点时,最长的合法括号序列对应的 R i R_i Ri (即找出最大的 R i R_i Ri使 [Li, R i R_i Ri] 是一个合法括号序列)。

【输入格式】

输入的第一行包含两个整数 n, m,分别表示括号序列长度和操作次数。
第二行包含给定的括号序列,括号序列中只包含左括号和右括号。
接下来 m 行,每行描述一个操作。如果该行为 “1 L i L_i Li R i R_i Ri”,表示第一种操作,区间为 [ L i L_i Li, R i R_i Ri] ;如果该行为 “2 L i L_i Li” 表示第二种操作,左端点为 L i L_i Li

【输出格式】

对于每个第二种操作,输出一行,表示对应的 R i R_i Ri。如果不存在这样的 R i R_i Ri,请输出 0。

【样例】

【样例输入】
7 5
((())()
2 3
2 2
1 3 5
2 3
2 1
【样例输出】
4
7
0
0

【问题分析】

【解题代码】



试题 J: 异或三角

【问题描述】

给定 T 个数 n 1 , n 2 , ⋅ ⋅ ⋅ , n T n_1, n_2, · · · , n_T n1,n2,⋅⋅⋅,nT,对每个 n i n_i ni 请求出有多少组 a, b, c 满足:

  1. 1 ≤ a , b , c ≤ n i 1 ≤ a, b, c ≤ n_i 1a,b,cni;
  2. a ⊕ b ⊕ c = 0,其中 ⊕ 表示二进制按位异或;
  3. 长度为 a, b, c 的三条边能组成一个三角形。

【输入格式】

输入的第一行包含一个整数 T。
接下来 T 行每行一个整数,分别表示 n 1 , n 2 , ⋅ ⋅ ⋅ , n T n_1, n_2, · · · , n_T n1,n2,⋅⋅⋅,nT

【输出格式】

输出 T 行,每行包含一个整数,表示对应的答案。

【样例】

【样例输入】
2
6
114514
【样例输出】
6
11223848130

【问题分析】

【解题代码】



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值