第八届蓝桥杯(软件类)决赛C/C++B组真题题解
题目结构
| 题目 | 类型 | 分值 |
|---|---|---|
| 第一题 | 结果填空 | 15分 |
| 第二题 | 结果填空 | 47分 |
| 第三题 | 代码填空 | 25分 |
| 第四题 | 程序设计 | 35分 |
| 第五题 | 程序设计 | 79分 |
| 第六题 | 程序设计 | 99分 |
第一题 36进制
-
问题描述
对于16进制,我们使用字母A-F来表示10及以上的数字。
如法炮制,一直用到字母Z,就可以表示36进制。
36进制中,A表示10,Z表示35,AA表示370
你能算出 MANY 表示的数字用10进制表示是多少吗?输出
输出一个整数表示答案
-
解题思路
36进制。位权值为36。累加位权和即可。
-
代码
/**
*@filename:36进制
*@author: pursuit
*@CSDNBlog:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-02 21:47
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
//36进制。位权值为36.
void solve(){
//MANY
//cout<<ll(('M'-'A'+10)*powl(36,3)+('A'-'A'+10)*powl(36,2)+('N'-'A'+10)*36+('Y'-'A'+10))<<endl;
cout<<1040254<<endl;
}
int main(){
solve();
return 0;
}
-
答案
1040254 1040254 1040254
第二题 瓷砖样式
-
问题描述
小明家的一面装饰墙原来是 3 × 10 3\times10 3×10 的小方格。
现在手头有一批刚好能盖住 2 2 2个小方格的长方形瓷砖。
瓷砖只有两种颜色:黄色和橙色。
小明想知道,对于这么简陋的原料,可以贴出多少种不同的花样来。
小明有个小小的强迫症:忍受不了任何 2 × 2 2\times2 2×2的小格子是同一种颜色。
(瓷砖不能切割,不能重叠,也不能只铺一部分。另外,只考虑组合图案,请忽略瓷砖的拼缝)
显然,对于 2 × 3 2\times3 2×3 个小格子来说,口算都可以知道:一共 10 10 10种贴法。
但对于 3 × 10 3\times10 3×10 的格子呢?肯定是个不小的数目,请你利用计算机的威力算出该数字。

输出
输出一个整数表示答案
-
解题思路
对于这道题,我们很容易就想到利用 d f s dfs dfs暴力去解决,那么我们需要注意的就是如何去表示整个方格以及如何判断是否符合要求并判重。 对于表示整个方格,我们可以用字符串来表示,也可以用二维数值数组来表示,那么这样我们就需要说明各自代表的是什么,该题黄色我表示为 1 1 1,未填表示为 0 0 0,橙色表示为 − 1 -1 −1。那么对于判断是否符合要求,直接累加 2 × 2 2\times2 2×2的方格值即可,判断是否模 4 4 4为 0 0 0。这很方便,我们也很容易证明,若方格值颜色相同,那么自然是模 4 4 4为 0 0 0的。再回到判重,我们可以将二维数值数组转化为字符串放入 s e t set set中,也可以将二维数组转化为 1 1 1个值,这里采用二进制的权值,那么这样我们总能保证不相同得到的和是不相同的。
-
代码
/**
*@filename:瓷砖样式
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-29 17:05
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000 + 5;
const int mod = 1e9+7;
//对于这个题,我们可以直接用dfs模拟,那么方格表示我们可以用二维数组来实现。
//其中1表示黄色,0表示黄色,-1表示未填,注意用set判重,判重的方法我们可以采用倍增思想表示一个数。
int n,m;//n*m大小的方格。
int a[maxn][maxn];
set<int> t;
bool judge(){
//由于我们用值来表示,也就是说若存在2*2颜色相同的方格,那么其中的值一定模4为0。
for(int i = 0; i < n-1; i++){
for(int j = 0; j < m-1; j++){
if((a[i][j]+a[i+1][j]+a[i][j+1]+a[i+1][j+1])%4==0){
return false;
}
}
}
return true;
}
void add(){
int ans=0,temp=1;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
ans+=a[i][j]*temp;
temp*=2;
}
}
t.insert(ans);
}
void dfs(int step){
//用step来抽象化二维坐标。
if(step == n*m){
//由于下标从0开始,说明已经过完了n*m个坐标。开始判断。
if(judge()){
add();
}
return;
}
int x = step / m , y = step % m ;
if(a[x][y]!=-1)dfs(step+1);
else{
//判断是否能横着放或者竖着放。
if(x+1<n&&a[x+1][y]==-1){
//说明能竖着放。
a[x][y]=a[x+1][y]=1;
dfs(step+1);
a[x][y]=a[x+1][y]=0;
dfs(step+1);
a[x][y]=a[x+1][y]=-1;//复原。
}
//注意这里一定要两边都要抉择。
if(y+1<m&&a[x][y+1]==-1){
//说明能横着放。
a[x][y]=a[x][y+1]=1;
dfs(step+1);
a[x][y]=a[x][y+1]=0;
dfs(step+1);
a[x][y]=a[x][y+1]=-1;//复原。
}
}
}
void solve(){
//我们.要注意模拟。
memset(a,-1,sizeof(a));
dfs(0);
cout<<t.size()<<endl;//101466
}
int main(){
cin>>n>>m;
solve();
return 0;
}
-
答案
101466 101466 101466
第三题 希尔伯特曲线
-
问题描述
希尔伯特曲线是以下一系列分形曲线 Hn 的极限。我们可以把 Hn 看作一条覆盖 2 n × 2 n 2^n × 2^n 2n×2n 方格矩阵的曲线,曲线上一共有 2 n × 2 n 2^n × 2^n 2n×2n 个顶点(包括左下角起点和右下角终点),恰好覆盖每个方格一次。

Hn(n > 1)可以通过如下方法构造:
- 将 Hn-1 顺时针旋转90度放在左下角
- 将 Hn-1 逆时针旋转90度放在右下角
- 将2个 Hn-1 分别放在左上角和右上角
- 用3条单位线段把4部分连接起来
对于 Hn 上每一个顶点 p ,我们定义 p 的坐标是它覆盖的小方格在矩阵中的坐标(左下角是 ( 1 , 1 ) (1, 1) (1,1),右上角是 ( 2 n , 2 n ) (2^n, 2^n) (2n,2n),从左到右是X轴正方向,从下到上是Y轴正方向),
定义 p 的序号是它在曲线上从起点开始数第几个顶点(从1开始计数)。以下程序对于给定的 n ( n < = 30 ) n(n <= 30) n(n<=30)和p点坐标 ( x , y ) (x, y) (x,y),输出p点的序号。请仔细阅读分析源码,填写划线部分缺失的内容。
#include <stdio.h> long long f(int n, int x, int y) { //n代表的是方格矩阵Hn,大小为2^n*2^n if (n == 0) return 1;//由于递归到了H0,那么自然为1. int m = 1 << (n - 1);//即m=2^(n-1)次方。 if (x <= m && y <= m) { return f(n - 1, y, x); //这里表明,如果x和y是属于左下角的子矩阵,那么我们就递归去寻找,由于左下角的子矩阵是顺时针翻转的,那么我们需要更改x和y } if (x > m && y <= m) { //同理,这里则是右下角的子矩阵。根据前面的分析,这道题其实就易解了。注意这个子矩阵是逆时针翻转过来的。 //如果没有翻转,坐标是(x-m,y),现在需要翻转,则为(m-y+1,m-(x-m)+1) //return 3LL * m * m + f(n - 1, ________________ , m * 2 - x + 1); // 填空 return 3LL * m * m + f(n - 1, m-y+1 , m * 2 - x + 1); // 填空 } if (x <= m && y > m) { //这里为左上角的子矩阵,那么我们需要加上已经过去了的m*m的序号。 //这里坐标我们是需要更改的,因为我们递归到左上角的Hn-1矩阵了,那么坐标自然要更改。 return 1LL * m * m + f(n - 1, x, y - m); } if (x > m && y > m) { //这里为右上角的子矩阵,那么我们需要加上已经过去了的2*m*m的序号。 return 2LL * m * m + f(n - 1, x - m, y - m); } } int main() { int n, x, y; scanf("%d %d %d", &n, &x, &y); printf("%lld", f(n, x, y));//给定p点的坐标,输出p点的序号。 return 0; } -
解题思路
题目分析已贴于代码中,这道题主要就是理清题意即可。
-
答案
m-y+1
第四题 发现环
-
问题重现
小明的实验室有N台电脑,编号1~N。原本这N台电脑之间有N-1条数据链接相连,恰好构成一个树形网络。
在树形网络上,任意两台电脑之间有唯一的路径相连。
不过在最近一次维护网络时,管理员误操作使得某两台电脑之间增加了一条数据链接,于是网络中出现了环路。
环路上的电脑由于两两之间不再是只有一条路径,使得这些电脑上的数据传输出现了BUG。
为了恢复正常传输。小明需要找到所有在环路上的电脑,你能帮助他吗?输入
第一行包含一个整数N。
以下N行每行两个整数a和b,表示a和b之间有一条数据链接相连。
对于30%的数据, 1 < = N < = 1000 1 <= N <= 1000 1<=N<=1000
对于100%的数据, 1 < = N < = 100000 , 1 < = a , b < = N 1 <= N <= 100000, 1 <= a, b <= N 1<=N<=100000,1<=a,b<=N
输入保证合法。输出
按从小到大的顺序输出在环路上的电脑的编号,中间由一个空格分隔。
样例输入
5 1 2 3 1 2 4 2 5 5 3样例输出
1 2 3 5 -
解题思路
-
拓扑排序
我们都知道拓扑排序实际上仅能适用于有向无环图的,那这里如果我们要强行应用在无向图中,应该要注意什么?首先,现在入度为 1 1 1和 0 0 0的都是起点,而不再是以入度为 0 0 0作为标志了,同样,由于是无向图,我们需要认为一条边上的两个顶点入度都需要增加。知道了这样,实际上我们就可以处理了,最后没有入队的即是存在环的。
-
并查集处理连通+dfs查找环
并查集处理这道题其实是非常方便的,我们怎么判断出现环了呢?即是当所加入的边上的两个顶点已经连通在一起了,我们如果再次连接就会出现环路。那么查找环也是一个问题,我们需要确定环路的起点和终点,而结束状态则是起点已经到达了终点,利用dfs的思想搜索路径,值得注意的是,我们需要还原状态,因为可能我们当前走的路径并不是正确的,所以必须还原到上一个状态。并且,当我们已经搜索完回路之后,我们需要标记我们已经找到了,避免重新开始而进入死循环。
-
-
代码
- 拓扑排序
/**
*@filename:发现环(拓扑排序)
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-29 19:32
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
//我们需要使用拓扑排序去解决这道题。
int indegree[maxn];//indegree[i]表示的即是顶点i的入度有多少。
bool vis[maxn];//vis[i]表示顶点i是否已经入队。
int n,a,b;
vector<int> g[maxn];
void solve(){
//现在我们需要去判断哪些点的入队为0.
queue<int> q;
for(int i=1;i<=n;i++){
if(indegree[i]==1){
q.push(i);//由于是无向图,所以我们默认为1的为起点。
vis[i]=true;
}
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<g[u].size();i++){
if(indegree[g[u][i]]>1){
indegree[g[u][i]]--;
if(indegree[g[u][i]]==1){
vis[g[u][i]]=true;
q.push(g[u][i]);
}
}
}
}
bool flag=false;
for(int i=1;i<=n;i++){
if(!vis[i]){
//没有入队的说明
if(!flag){
//说明是第一次输出。
cout<<i;
flag=true;
}
else{
cout<<" "<<i;
}
}
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a>>b;
indegree[a]++,indegree[b]++;
g[a].push_back(b);
g[b].push_back(a);
}
solve();
return 0;
}
- 并查集处理连通+dfs查找环
/**
*@filename:发现环
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-29 19:06
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n,a,b;//n台电脑。
int father[maxn];
vector<int> ans;//存储出现环路的电脑。
vector<int> g[maxn];//无向图。
bool vis[maxn];//vis[i]表示i是否已经进入结果序列。
vector<int> result;//结果序列。
bool flag;//判断是否找到回路。
int find(int x){
int r=x;
while(r!=father[r])r=father[r];
int i=x,j;
while(father[i]!=r){
j=father[i];
father[i]=r;
i=j;
}
return r;
}
void dfs(int st,int ed){
if(st==ed){
//说明回路已经搜索完成,此时直接退出。
sort(result.begin(),result.end());
for(int i=0;i<result.size();i++){
printf("%d%c",result[i],i==result.size()-1?'\n':' ');
}
flag=true;
return;
}
for(int i=0;i<g[st].size();i++){
int u=g[st][i];
if(!vis[u]&&!flag){
result.push_back(u);
vis[u]=true;
dfs(u,ed);
//回溯。
result.pop_back();
vis[u]=false;
}
}
return;
}
int main(){
scanf("%d",&n);
int st,ed;
for(int i=1;i<=n;i++)father[i]=i;
for(int i=1;i<=n;i++){
scanf("%d%d",&a,&b);
g[a].push_back(b),g[b].push_back(a);
int fa=find(a),fb=find(b);
if(fa!=fb){
father[fa]=fb;
}
else{
//说明a和b已经连接了,此时再次连接则会出现环路。所以我们可以记录环路的起点和终点。
st=a,ed=b;
}
}
vis[st]=true;
result.push_back(st);
dfs(st,ed);
return 0;
}
第五题 对局匹配
-
问题描述
小明喜欢在一个围棋网站上找别人在线对弈。这个网站上所有注册用户都有一个积分,代表他的围棋水平。
小明发现网站的自动对局系统在匹配对手时,只会将积分差恰好是K的两名用户匹配在一起。
如果两人分差小于或大于 K K K,系统都不会将他们匹配。
现在小明知道这个网站总共有 N N N名用户,以及他们的积分分别是 A 1 , A 2 , . . . A N A_1, A_2, ... A_N A1,A2,...AN。
小明想了解最多可能有多少名用户同时在线寻找对手,但是系统却一场对局都匹配不起来(任意两名用户积分差不等于K)?输入
第一行包含两个个整数 N N N和 K K K。第二行包含 N N N个整数 A 1 , A 2 , . . . A N A_1, A_2, ... A_N A1,A2,...AN。
对于30%的数据, 1 < = N < = 10 1 <= N <= 10 1<=N<=10
对于100%的数据, 1 < = N < = 100000 , 0 < = A i < = 100000 , 0 < = K < = 100000 1 <= N <= 100000, 0 <= Ai <= 100000, 0 <= K <= 100000 1<=N<=100000,0<=Ai<=100000,0<=K<=100000输出
一个整数,代表答案。
样例输入
10 0 1 4 2 8 5 7 1 4 2 8样例输出
6 -
解题思路
-
动态规划
题目所求的即是让我们尽可能找到多的人使他们任意之间积分之差不等于 k k k,我们用 c n t cnt cnt数组来记录积分的出现次数,那么我们就可以通过这个 k k k来分组,然后枚举 i + k j ( i ∈ [ 0 , k − 1 ] , j ∗ k < = m a x x ) i+kj(i\in[0,k-1],j*k<=maxx) i+kj(i∈[0,k−1],j∗k<=maxx),这样,我们就存在着选与不选了,跟背包问题来分析即可。若选择此时的 i + k j i+kj i+kj,那么就不能继承上一次的 i + k ∗ ( j − 1 ) i+k*(j-1) i+k∗(j−1)了,而是需要加上 i + k ∗ ( j − 2 ) i+k*(j-2) i+k∗(j−2),而若不选择,则就是继承上一次的 i + ( j − 1 ) k i+(j-1)k i+(j−1)k。最后,累加所有的最终状态值即可得到答案。
-
贪心+尺取法
我们知道如果两个人之间积分值相差为k,那么我们随便取一个出来就可以凑出一个答案了。按照这思想尺取法遍历所有积分,最后累加即可。
-
-
代码
- 动态规划
/**
*@filename:对局匹配
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-29 20:19
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n,k,temp,maxx;//题目所问,即为任意两名用户的积分之差不等于K。
int cnt[maxn];//cnt[i]表示积分为i的出现次数。
int dp[maxn];//dp数组。有点像背包问题,我们让相差为k分的人为为一组。
void solve(){
int ans=0;
if(!k){
//说明k为0,此时我们只需要统计积分不相同的即可。
for(int i=0;i<=maxx;i++){
if(cnt[i])ans++;
}
}
else{
//dp,我们通过k将其分成0~k-1组。
for(int i=0;i<k;i++){
int j;
for(j=i;j<=maxx;j+=k){
//对j进行情况分析。
if(j-2*k>=0){
//说明可以选择j也可以不选择。如果不选择,那么就是继承上一次的dp[j-k];
dp[j]=max(dp[j-k],dp[j-2*k]+cnt[j]);//如果选择,就是加上上一次的dp[j-2*k]。
}
else if(j-k>=0){
//说明此时仍是选还是不选。
dp[j]=max(dp[j-k],cnt[j]);
}
else{
dp[j]=max(dp[j],cnt[j]);
}
}
//由于退出的时候j已经大于maxx了,所以我们需要减去k。
ans+=dp[j-k];
}
}
printf("%d\n",ans);
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&temp);
cnt[temp]++;
maxx=max(maxx,temp);//保存当前最大值。
}
solve();
return 0;
}
- 贪心+尺取法
/**
*@filename:对局匹配-贪心
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-29 20:52
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
//贪心+尺取法,我们知道如果两个人之间积分值相差为k,那么我们随便取一个出来就可以凑出一个答案了。
int n,k,a[maxn];
bool vis[maxn];//vis[i]判断i是否已经被确定了,即存在和别人积分值相差k。
void solve(){
sort(a+1,a+n+1);
int ans=0;
int l=1,r=1;
while(r<=n){
if(l==r){
r++;
continue;
}
if(a[l]==a[r]-k&&!vis[l]&&!vis[r]){
//说明没有被确认,我们取一个凑答案。
ans++;
vis[l]=vis[r]=true;
l++,r++;
}
else if(a[l]<a[r]-k&&!vis[l]&&!vis[r]){
//说明凑不出,我们需要偏移,让l靠近r。
l++;
}
else if(a[l]>a[r]-k&&!vis[l]&&!vis[r]){
//说明凑不出,且a[l]>a[r],我们需要偏移,让r大于它。
r++;
}
else{
//说明存在l和r有一个是被确认的,我们需要移动被确认的。
if(vis[l])l++;
else if(vis[r])r++;
else if(vis[l]&&vis[r]){
l++,r++;
}
}
}
for(int i=1;i<=n;i++){
if(!vis[i]){
//说明无法凑出,我们累加。
ans++;
}
}
printf("%d\n",ans);
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
solve();
return 0;
}
第六题 观光铁路
-
问题描述
跳蚤国正在大力发展旅游业,每个城市都被打造成了旅游景点。
许多跳蚤想去其他城市旅游,但是由于跳得比较慢,它们的愿望难以实现。
这时,小C听说有一种叫做火车的交通工具,在铁路上跑得很快,便抓住了商机,创立了一家铁路公司,向跳蚤国王请示在每两个城市之间都修建铁路。
然而,由于小C不会扳道岔,火车到一个城市以后只能保证不原路返回,而会随机等概率地驶向与这个城市有铁路连接的另外一个城市。
跳蚤国王向广大居民征求意见,结果跳蚤们不太满意,因为这样修建铁路以后有可能只游览了3个城市(含出发的城市)以后就回来了,它们希望多游览几个城市。
于是跳蚤国王要求小C提供一个方案,使得每只跳蚤坐上火车后能多游览几个城市才回来。
小C提供了一种方案给跳蚤国王。跳蚤国王想知道这个方案中每个城市的居民旅游的期望时间(设火车经过每段铁路的时间都为1),请你来帮跳蚤国王。输入
输入的第一行包含两个正整数n、m,其中n表示城市的数量,m表示方案中的铁路条数。
接下来m行,每行包含两个正整数u、v,表示方案中城市u和城市v之间有一条铁路。
保证方案中无重边无自环,每两个城市之间都能经过铁路直接或间接到达,且火车由任意一条铁路到任意一个城市以后一定有路可走。
4 <= k <= n <= 21,1 <= u, v <= n输出
输出n行,第i行包含一个实数ti,表示方案中城市i的居民旅游的期望时间。
你应当输出足够多的小数位数,以保证输出的值和真实值之间的绝对或相对误差不超过1e-9。样例输入
4 5 1 2 2 3 3 4 4 1 1 3样例输出
3.333333333333 5.000000000000 3.333333333333 5.000000000000 -
解题思路
待补。贴出AC代码供各位参考。
-
代码
#include<iostream>
using namespace std;
const int MAX = 100;
int num[MAX];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
num[u]++;
num[v]++;
}
for (int i = 1; i <= n; i++) {
double p = m * 2.0 / num[i];
printf("%.12f\n", p);
}
return 0;
}

浙公网安备 33010602011771号