2021年第十二届蓝桥杯决赛 C++ B组
试题 A:带宽
【问题描述】
小蓝家的网络带宽是 200 Mbps,请问,使用小蓝家的网络理论上每秒钟最多可以从网上下载多少MB的内容。
【答案】:25
很简单,没什么好说的。
#include <iostream>
using namespace std;
int main()
{
cout<<200/8;
return 0;
}
试题 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 中,有多少个纯质数?
【答案】:1903
填空题,能跑出来就行。先检验每位是不是质数,再检验这个数是不是质数,会快一点。大概20秒也就跑完了。
#include <iostream>
using namespace std;
bool isZhiShu(int num)
{
int temp = num;
while(temp)
{
int t = temp%10;
if(t!=2 && t!=3 && t!=5 && t!=7){return false;}
temp /= 10;
}
for(int i=2;i<num;i++)
{
if(num%i==0){return false;}
}
return true;
}
int main()
{
int result = 0;
for(int i=2;i<=20210605;i++)
{
if(isZhiShu(i))
{
result++;
}
}
cout<<result;
return 0;
}
试题 C:完全日期
【问题描述】
如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日期。
例如:2021 年 6 月 5 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 5 = 16 2+0+2+1+6+5=16 2+0+2+1+6+5=16 ,而 16 是一个完全平方数,它是 4 的平方。所以 2021 年 6 月 5 日是一个完全日期。
请问, 从 2001 年 1 月 1 日到 2021 年 12 月 31 日中,一共有多少个完全日期?
【答案】:977
#include <iostream>
#include <unordered_map>
using namespace std;
int y, m, d;
int dom[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
unordered_map<int,int> c;
int sum(int num)
{
int result = 0;
while(num)
{
result += num%10;
num /= 10;
}
return result;
}
int main()
{
for(int i=0;i<10;i++){c[i*i]=1;}
int result = 0;
y=2001;m=1;d=1;
while(true)
{
if(c[ sum(y)+sum(m)+sum(d) ]==1){result++;}
if(y==2021 && m==12 && d==31){break;}
if(++d>dom[m])
{
d=1;
if(++m>12)
{
m = 1;
y++;
if((y%4==0 && y%100!=0)||y%400==0)
{dom[2]=29;}
else
{dom[2]=28;}
}
}
}
cout<<result;
return 0;
}
试题 D:最小权值
【问题描述】
对于一棵有根二叉树 T T T,小蓝定义这棵树中结点的权值 W ( T ) W(T) W(T) 如下:
空子树的权值为 0 0 0
如果一个节点 v v v 有左子树 L L L ,右子树 R R R ,分别有 C ( L ) C(L) C(L) 和 C ( R ) C(R) C(R) 个结点,则 W ( v ) = 1 + 2 W ( L ) + 3 W ( R ) + ( C ( L ) ) 2 C ( R ) W(v) = 1+2W(L)+3W(R)+(C(L))^2C(R) W(v)=1+2W(L)+3W(R)+(C(L))2C(R)。
树的权值定义为树的根结点的权值。
小蓝想知道,对于一棵有 2021 个节点的二叉树,树的权值最小可能是多少?
【答案】:2653631372
一眼dp。
#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map<int,int> visited;
unordered_map<int,long long int> dpmap;
long long int dp(int num)
{
if(num==0){return 0;}
if(visited[num]==1){return dpmap[num];}
long long int result = 1e10;
for(int i=0;i<num;i++)
{
long long int w = 1 + 2*dp(i) + 3*dp(num-i-1) + i*i*(num-i-1);
if(w<result){result = w;}
}
visited[num] = 1;
dpmap[num] = result;
return result;
}
int main()
{
cout<<dp(2021);
return 0;
}
试题 E:大写
【问题描述】
给定一个只包含大写字母和小写字母的字符串,请将其中所有小写字母转换成大写字母后将字符串输出。
【输入格式】
输入一行包含一个字符串。
【输出格式】
输出转换成大写后的字符串。
【答案】
#include <iostream>
using namespace std;
int main()
{
string s;
cin>>s;
for(int i=0;i<s.size();i++)
{
if(s[i]>='a' && s[i]<='z')
{
s[i] = s[i] - 'a' + 'A';
}
}
cout<<s;
return 0;
}
试题 F:123
【问题描述】
小蓝发现了一个有趣的数列,这个数列的前几项如下:
1 , 1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , 4 , … … 1,1,2,1,2,3,1,2,3,4,…… 1,1,2,1,2,3,1,2,3,4,……
小蓝发现,这个数列前 1 项是整数 1 ,接下来 2 项是整数 1 至 2 ,接下来 3 项是整数 1 至 3 ,接下来 4 项是整数 1 至 4 ,依次类推。
小蓝想知道,这个数列中,连续一段的和是多少。
【输入格式】
输入的第一行包含一个整数 T T T ,表示询问的个数。
接下来 T T T 行,每行包含一组询问,其中第 i i i 行包含两个整数 l i l_i li 和 r i r_i ri ,表示询问数列中第 l i l_i li 个数到第 r i r_i ri 个数的和。
【输出格式】
输出 T T T 行,每行包含一个整数表示对应询问的答案。
【答案】
自然数列求和是吧,我先放个自然数列求和公式在这: S ( n ) = ( 1 + n ) n 2 S(n) = \frac {(1+n)n}{2} S(n)=2(1+n)n , 那第 s s s 项到第 e e e 项求和公式就是 $ S(e) - S(s-1) = \frac {(s+e)(e-s+1)}{2}$
然后想到分组。可以将数列第 n n n 个数转换为:数列第 k n k_n kn 组,第 j n j_n jn 个数。每一组中数字个数与组号 k k k 相同。
其中,组号 k k k 可以通过求解一元二次方程 $\frac {(1+k)*k} 2 = n $ 再向上取整得到,用求根公式就行。(我因为比赛前刚考了门金融数值计算,然后我惯性地写了个牛顿迭代,出来之后才想起来,笑死)当然,要取正的那个。 k = ⌈ − 1 + 1 + 8 n 2 ⌉ k = \lceil \frac {-1+ \sqrt {1+8n}} {2} \rceil k=⌈2−1+1+8n⌉ ,所以 j = n − ( k − 1 ) k 2 j = n - \frac {(k-1)k}{2} j=n−2(k−1)k
然后就可以写出数列求和的公式了 。
S ( n ) = { ∑ r = 1 j r k = 1 ∑ r = 1 k − 1 ∑ m = 1 r m + ∑ r = 1 j r k > 1 S\left( n \right) =\begin{cases} \sum_{r=1}^j{r}& k=1\\ \sum_{r=1}^{k-1}{\sum_{m=1}^r{m}}+\sum_{r=1}^j{r}& k>1\\ \end{cases} S(n)={∑r=1jr∑r=1k−1∑m=1rm+∑r=1jrk=1k>1
接下来就看 k > 1 k > 1 k>1 的情况了
带入求和公式之后就是:
S ( n ) = ∑ k = 1 k − 1 ( 1 + k ) k 2 + ( 1 + j ) j 2 S(n) = \sum_{k=1}^{k-1} \frac {(1+k)k}{2} + \frac {(1+j)j}{2} S(n)=k=1∑k−12(1+k)k+2(1+j)j
然后中间其实是一个等差数列和一个平方数求和,于是进一步带公式 1 2 + 2 2 + 3 2 + ⋯ + n 2 = n ( n + 1 ) ( 2 n + 1 ) 6 1^2+2^2+3^2+ \cdots +n^2 = \frac{n(n+1)(2n+1)}{6} 12+22+32+⋯+n2=6n(n+1)(2n+1)
S ( n ) = ( k − 1 ) k ( 2 k − 1 ) 12 + k ( k − 1 ) 4 + ( 1 + j ) j 2 S(n) = \frac {(k-1)k(2k-1)}{12} + \frac {k(k-1)}{4} + \frac {(1+j)j}{2} S(n)=12(k−1)k(2k−1)+4k(k−1)+2(1+j)j
最后只要求 S ( r ) − S ( l − 1 ) S(r)-S(l-1) S(r)−S(l−1) 即可。
但我平方和公式现场做的时候没想起来,只能过部分用例了。
#include <iostream>
#include <math.h>
using namespace std;
long long int solvek(long long int num)
{
float n = num;
return ceil((-1+sqrt(1 + 8*n))/2);
}
long long int solvej(long long int n, long long int k)
{
return n - ((k-1)*k)/2;
}
long long int sumn(long long int e)
{
return (1+e)*e/2;
}
long long int sums(long long int n)
{
return n*(n+1)*(2*n+1)/6;
}
long long int solve(long long int n)
{
long long int k = solvek(n);
long long int j = solvej(n, k);
return (sums(k-1)+sumn(k-1))/2 + sumn(j);
}
int main()
{
int T;
cin>>T;
while(T--)
{
long long int l, r;
cin>>l>>r;
cout<<solve(r)-solve(l-1)<<endl;
}
return 0;
}
试题 G:异或变换
【问题描述】
小蓝有一个 01 串 s = s 1 s 2 s 3 ⋯ s n s=s_1s_2s_3 \cdots s_n s=s1s2s3⋯sn。
以后每个时刻,小蓝要对这个 01 串进行一次变换。每次变换的规则相同。
对于 01 串 s = s 1 s 2 s 3 ⋯ s n s=s_1s_2s_3 \cdots s_n s=s1s2s3⋯sn ,变换后的 01 串 s ′ = s 1 ′ s 2 ′ s 3 ′ ⋯ s n ′ s^{'}=s^{'}_1s^{'}_2s^{'}_3 \cdots s^{'}_n s′=s1′s2′s3′⋯sn′ 为:
s 1 ′ = s 1 s^{'}_1 = s_1 s1′=s1 ;
s i ′ = s i − 1 ⊕ s i s^{'}_i = s_{i-1} \oplus s_i si′=si−1⊕si 。
其中 a ⊕ b a \oplus b a⊕b 表示两个二进制的异或,当 a a a 和 b b b 相同时结果为 0,当 a a a 和 b b b 不同时结果为 1。
请问,经过 t t t 次变换后的 01 串是什么?
【输入格式】
输入的第一行包含两个整数 n , t n,t n,t ,分别表示 01 串的长度和变换的次数。
第二行包含一个长度为 n n n 的 01 串。
【输出格式】
输出一行包含一个 01 串,为变换后的串。
【答案】
不会,暴力了。
#include <iostream>
using namespace std;
int main()
{
string s;
int n,t;
cin>>n>>t;
cin>>s;
while(t--)
{
string t = s;
for(int i=1;i<n;i++)
{
if(s[i-1]==s[i])
{t[i]='0';}
else
{t[i]='1';}
}
if(t==s){break;}
s = t;
}
cout<<s<<endl;
return 0;
}
试题 H:二进制问题
【问题描述】
小蓝最近在学习二进制。他想知道 1 到 N N N 中有多少个数满足其二进制表示中恰好有 K K K 个 1 。你能帮助他吗?
【输入格式】
输入一行包含两个整数 N N N 和 K K K。
【输出格式】
输出一个整数表示答案。
【答案】
对不起,小蓝。我有一个绝妙的想法,可惜时间不够了。暴力了。
#include <iostream>
using namespace std;
int n,k;
int mypow(int n, int k)
{
if(k==1){return n;}
int temp = mypow(n, k/2);
if(k%2==1)
{return temp*temp*n;}
else
{return temp*temp;}
}
bool isTrue(int num)
{
int n = 0;
while(num)
{
if(num%2==1)
{
if(++n>k){return false;}
}
num /= 2;
}
if(n!=k){return false;}
return true;
}
int main()
{
int result=0;
cin>>n>>k;
for(int i=mypow(2,k)-1;i<=n;i++)
{
if(isTrue(i))
{
result++;
}
}
cout<<result<<endl;
return 0;
}
试题 I:翻转括号序列
【问题描述】
给定一个长度为 n n n 的括号序列,要求支持两种操作;
1.将 [ L i , R i ] [L_i,R_i] [Li,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 使 [ L i , R i ] [L_i,R_i] [Li,Ri] 是一个合法括号序列。
【输入格式】
输入的第一行包含两个整数 n , m n,m n,m ,分别表示括号序列长度和操作次数。
第二行包含给定的括号序列,括号序列中只包含左括号和右括号。
接下来 m m m 行,每行描述一个操作。如果该行为“1 L i R i L_i R_i LiRi”,表示第一种操作,区间为 [ L i , R i ] [L_i,R_i] [Li,Ri];如果该行为“2 L i L_i Li”表示第二种操作,左端点为 L i L_i Li。
【输出格式】
对于每个第二种操作,输出一行,表示对应的 R i R_i Ri。如果不存在这样的 R i R_i Ri,请输出 0。
【答案】
一眼线段树,括号用01串替换。诶(éi),但是我不会。日常暴力。
#include <iostream>
#include <string>
using namespace std;
int n,m;
string s;
void change(int l, int r)
{
for(int i=l-1;i<r;i++)
{
if(s[i]=='(')
{s[i]=')';}
else
{s[i]='(';}
}
return;
}
int solve(int l)
{
int lc = 0;
int result = 0;
for(int i=l-1;i<n;i++)
{
if(s[i]=='(')
{lc++;}
else
{
lc--;
if(lc<0){break;}
}
if(lc==0){result=i+1;}
}
return result;
}
int main()
{
cin>>n>>m;
cin>>s;
while(m--)
{
int type;
cin>>type;
if(type==1)
{
int l, r;
cin>>l>>r;
change(l, r);
}
else
{
int l;
cin>>l;
cout<<solve(l);
}
}
return 0;
}
试题 J:异或三角
【问题描述】
给定 T T T 个数 n 1 , n 2 , ⋯ , n T n_1,n_2,\cdots,n_T n1,n2,⋯,nT ,对每个 n i n_i ni 请求出有多少组 a , b , c a,b,c a,b,c 满足:
1. $1\le a,b,c \le n_i$; 2. $a \oplus b \oplus c = 0$ ,其中 $\oplus$ 表示二进制按位异或; 3. 长度为 $a,b,c$ 的三条边能组成一个三角形。
【输入格式】
输入的第一行包含一个整数 T T T。
接下来 T T T 行每行一个整数, 分别表示 n 1 , n 2 , ⋯ , n T n_1,n_2,\cdots,n_T n1,n2,⋯,nT。
【输出格式】
输出 T T T 行,每行包含一个整数,表示对应的答案。
【答案】
不会,随便写了个dp,114514都没过(笑死)。
#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map<int,int> visited;
unordered_map<int,int> dpmap;
int dp(int n)
{
if(n==1){return 0;}
if(visited[n]==1){return dpmap[n];}
int result = dp(n-1);
for(int i=n/2;i<n;i++)
{
for(int j=i-1;j>0;j--)
{
if(i+j<=n){continue;}
if((i^j^n) == 0){result++;}
}
}
visited[n] = 1;
dpmap[n] = result;
return result;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
cout<<dp(n);
}
return 0;
}
【总结】
数学与应用数学专业来玩一玩,基本上啥也不会,结果居然是国二,这得奖率还是高的呀。可惜今年不在北京,也没个颁奖典礼啥的,可惜了。然后,比赛的时候没带吃的,但旁边的同学带了一大袋,不时吃一口,给我整饿了。