【蓝桥杯】第十一届省塞模拟赛C/C++组(题目+题解)

文章目录

          • 1.排列字母LANQIAO
          • 2.单位变换
          • 3.括号的合法序列
          • 4.2019个节点的无向连通图
          • 5.反倍数
          • 6.凯撒密码
          • 7.螺旋矩阵
          • 8.摆动序列
          • 9.村庄通电
          • 10.种树问题




1.排列字母LANQIAO

问题描述
  将LANQIAO中的字母重新排列,可以得到不同的单词,如LANQIAO、AAILNOQ等,注意这7个字母都要被用上,单词不一定有具体的英文意义。
  请问,总共能排列如多少个不同的单词。
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


答案:2520
解决思路:其实这就是一道高中时器的选择题,考点是“排列组合”。
如果题目问你:7个不同字母进行排列,问你能组合出多少种不同的序列,你可以很容易得到答案为:7!=7×6×5×4×3×2×1=5040。但是别急!!!本题的答案并不是5040,因为本题给的7个字母并不是完全不同的,因为其中有两个“A”。因此在刚才的计算中,我们将两个字母“A”视为了不同的字母。解决办法很简单,那就是再除以2,即正确答案应该是:7! / 2 = 7×6×5×4×3×2×1 / 2 = 5040 /2 = 2520。

有同学肯定要问,那要是我偏让你输出这2520个具体的排列呢?那没办法了,上代码:

#include
#include
using namespace std;
int main()
{
	char chs[]={'A','A','I','L','N','O','Q'};
	int sum=0;
	cout<<"具体的排列情况如下:"<<endl;
	do{
		sum++;
		for(int i=0;i<7;i++) cout<<chs[i];
		cout<<endl;
	}while(next_permutation(chs,chs+7));
	cout<<"共"<<sum<<"个"<<endl;
	return 0;
}



2.单位变换

问题描述
  在计算机存储中,12.5MB是多少字节?
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


答案:13107200
解决办法:作为一名程序员,你至少要知道:1MB(兆字节) = 1024KB(千字节),1KB = 1024B(字节)。
因此对于本题而言,可以直接用电脑自带的“计算器”软件计算:12.5×1024×1024=13107200。




3.括号的合法序列

问题描述
  由1对括号,可以组成一种合法括号序列:()。
  由2对括号,可以组成两种合法括号序列:()()、(())。
  由4对括号组成的合法括号序列一共有多少种?
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


答案:14
解决方案一:
4对括号这个数据并不算大,所以可以直接硬算:
深度为1时:()()()();
深度为2时:(())()()、()(())()、()()(())、(()()())、(()())()、()(()())、(())(());
深度为3时:((()))()、()((()))、((())())、(()(()))、((()()));
深度为4时:(((())))。

解决方案二:
从长远的方向来看,指不定下一次省赛就将这道题变成了编程大题,因此我们还是要给出本题的代码实现:

#include
using namespace std;

int sum,n;
void dfs(int left,int right)			//用left和right分别表示(和),由于给定的(和)最初是匹配的 
{
	if(left == n){						//故所有的非法情况都是由)导致的,因此当(都出现了就不会非法 
		sum++;
		return;
	}
	dfs(left+1,right);					//根据贪心思想,合法的括号组合方式必然是以(开始 
	if(left > right) dfs(left,right+1); //基于前面的结果,此后在满足left>right时得到的括号序列都将是合法的 
}

int main()
{
	cin>>n; 
	dfs(0,0);
	cout<<sum<<endl;
	return 0;
}

如果题目还要求输出具体的合法序列,则程序可添加为如下内容:

#include
#include
using namespace std;

const int N=1000;
char chs[N];
int sum,n;
void dfs(int left,int right) 
{
	if(left == n){ 
		sum++;
		cout<<chs;
		for(int i=strlen(chs);i<n*2;i++) cout<<")";
		cout<<endl;
		return;
	}
	chs[left+right]='(';
	dfs(left+1,right); 
	chs[left+right]='\0'; 
	if(left > right){
		chs[left+right]=')';
		dfs(left,right+1);
	}
} 

int main()
{
	cin>>n;
	cout<<"总的括号匹配序列有:"<<endl;	
	dfs(0,0);
	cout<<"共"<<sum<<"个"<<endl; 
	return 0;
}



4.2019个节点的无向连通图

问题描述
  一个包含有2019个结点的无向连通图,最少包含多少条边?
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


答案:2018
解决办法:一个具有n个节点的无向连通图,其最少含有的边为n-1(其实就是这个图的生成树)。




5.反倍数

问题描述
  给定三个整数 a, b, c,如果一个整数既不是 a 的整数倍也不是 b 的整数倍还不是 c 的整数倍,则这个数称为反倍数。
  请问在 1 至 n 中有多少个反倍数。
  
输入格式
  输入的第一行包含一个整数 n。
  第二行包含三个整数 a, b, c,相邻两个数之间用一个空格分隔。
输出格式
  输出一行包含一个整数,表示答案。
  
样例输入
30
2 3 6
样例输出
10

样例说明
  以下这些数满足要求:1, 5, 7, 11, 13, 17, 19, 23, 25, 29。
评测用例规模与约定
  对于 40% 的评测用例,1 <= n <= 10000。
  对于 80% 的评测用例,1 <= n <= 100000。
  对于所有评测用例,1 <= n <= 1000000,1 <= a <= n,1 <= b <= n,1 <= c <= n。


分析:由于数据范围并不大,且计算过程仅仅是3次取余操作,那么在极限情况下,也仅仅是执行100 0000×3次取余操作,以及不多于100 0000次的累加操作。所以可以直接采用暴力法,代码如下:

#include 
using namespace std;

int count(int n,int a,int b,int c)
{
	int ans=0;
	for(int i=1;i<=n;i++)
		if(i%a!=0 && i%b!=0 && i%c!=0) ans++;
	return ans;
} 

int main() 
{
    int n,a,b,c;
    cin>>n>>a>>b>>c;
    cout<<count(n,a,b,c);
    return 0;
}



6.凯撒密码

问题描述
  给定一个单词,请使用凯撒密码将这个单词加密。
  凯撒密码是一种替换加密的技术,单词中的所有字母都在字母表上向后偏移3位后被替换成密文。即a变为d,b变为e,…,w变为z,x变为a,y变为b,z变为c。
  例如,lanqiao会变成odqtldr。
  
输入格式
  输入一行,包含一个单词,单词中只包含小写英文字母。
输出格式
  输出一行,表示加密后的密文。
  
样例输入
lanqiao
样例输出
odqtldr

评测用例规模与约定
  对于所有评测用例,单词中的字母个数不超过100。


分析:题目明确了输入是一个单词,且仅含小写字母,那么处理起来就很简单了。我们直接接受一个字符串str,然后遍历该字符串中的每个字符,将每个字符都与字符’a’进行做差(目的是得到当前字符在小写字母表中的位置,如’a’为0,'b’为1,'c’为2,……以此类推),注意此时得到的差值是一个int型。由于凯撒密码的加密方案是往后移3个位置,那么我们就需要将做差得到的这个差值+3。
这里有一点需要注意:+3可能导致这个值大于26(这样在还原的时候,就会得到非小写字母类字符),因此我们在这里还需要对+3后的这个值进行一个取余运算,代码描述如下:

for(int i-0;i<str.length();i++)
	int temp = (str[i]-'a'+3)%26;

接下来就是还原工作了。我们可以直接将得到的temp与字符’a’进行求和运算,此时temp就由int型变成了char型(如0+‘a’=‘a’,1+‘a’=‘b’,2+‘a’=‘c’,……以此类推)。这一过程的代码描述如下:

for(int i-0;i<str.length();i++){
	int temp = (str[i]-'a'+3)%26;
	str[i] = 'a' + temp;
}

求解本题的完整代码如下:

#include
#include
using namespace std;

string Caesar(string str)
{
	for(int i=0;i<str.length();i++)
		str[i]='a'+(str[i]-'a'+3)%26;
	return str;
}

int main()
{
	string str;
	cin>>str;
	cout<<Caesar(str)<<endl;
	return 0;
}



7.螺旋矩阵

问题描述
  对于一个 n 行 m 列的表格,我们可以使用螺旋的方式给表格依次填上正整数,我们称填好的表格为一个螺旋矩阵。
  例如,一个 4 行 5 列的螺旋矩阵如下:
  1 2 3 4 5
  14 15 16 17 6
  13 20 19 18 7
  12 11 10 9 8
  
输入格式
  输入的第一行包含两个整数 n, m,分别表示螺旋矩阵的行数和列数。
  第二行包含两个整数 r, c,表示要求的行号和列号。
输出格式
  输出一个整数,表示螺旋矩阵中第 r 行第 c 列的元素的值。
  
样例输入
4 5
2 2
样例输出
15

评测用例规模与约定
  对于 30% 的评测用例,2 <= n, m <= 20。
  对于 70% 的评测用例,2 <= n, m <= 100。
  对于所有评测用例,2 <= n, m <= 1000,1 <= r <= n,1 <= c <= m。


分析:这是一道典型的模拟类题目。我们的求解思路很简单:首先是根据输入的规格求出对应螺旋矩阵,然后再通过输入的待求位置直接得到答案即可。
问题的关键点就在于怎么求出这个螺旋矩阵?直接解释很难说,请同学们直接看代码(附详细解释,走一遍就能懂)。需要注意的是一定要对count==m*n时进行特殊判断,防止由于正方形矩阵的出现而陷入死循环。

#include
#include
using namespace std;

const int N=1005;
int ary[N][N];
void create(int n,int m)
{
	int U=1,R=m,D=n,L=1;		//分别表示矩阵的上、右、下、左边界限
	int i=1,j=1,count=1;
	while(count<=m*n)
	{
		for(;j<R;j++) ary[i][j]=count++;	//从左往右填充
		for(;i<D;i++) ary[i][j]=count++;	//从上往下填充
		for(;j>L;j--) ary[i][j]=count++;	//从右往左填充 
		for(;i>U;i--) ary[i][j]=count++;	//从下往上填充
		U++,R--,D--,L++;
		i++,j++;
		if(count == m*n) ary[i][j]=count++;	//防止当m=n时陷入死循环 
	} 
}

int main()
{
	int n,m,r,l;
	cin>>n>>m>>l>>r;
	create(n,m);
	cout<<ary[l][r]<<endl;
	return 0;
} 



8.摆动序列

问题描述
  如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 a[2i]a[2i]。
  小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
  
输入格式
  输入一行包含两个整数 m,n。
输出格式
  输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
  
样例输入
3 4
样例输出
14

样例说明
  以下是符合要求的摆动序列:
  2 1 2
  2 1 3
  2 1 4
  3 1 2
  3 1 3
  3 1 4
  3 2 3
  3 2 4
  4 1 2
  4 1 3
  4 1 4
  4 2 3
  4 2 4
  4 3 4
  
评测用例规模与约定
  对于 20% 的评测用例,1 <= n, m <= 5;
  对于 50% 的评测用例,1 <= n, m <= 10;
  对于 80% 的评测用例,1 <= n, m <= 100;
  对于所有评测用例,1 <= n, m <= 1000。


分析:这是一道典型的动态规划题目,和蓝桥杯 校内模拟赛 奇怪的数列以及蓝桥杯 历届试题 波动数列类似,搜索算法只能对[1,10]的范围内的数据起作用,要想得满分必然要用动态规划。
博主对于动态规划一直都在学习中,因此在看到这道题时,我觉得很有必要为其单独写一篇题解(因为够难,所以也够资格为其单独写题解),这是链接【蓝桥杯】省塞模拟赛 摆动序列(动态规划),下面就直接为Ctrl C党贴满分代码了:

#include
using namespace std;

const int N=1005;
const int MOD=10000;
int dp[N][N];
int DP(int m,int n)
{
	for(int i=1;i<=n;i++) dp[1][i]=n-i+1;	//初始化dp数组
	for(int i=2;i<=m;i++){
		if(i&1)								//如果i为奇数 
			for(int j=n;j>=1;j--) 
				dp[i][j]=(dp[i-1][j-1]+dp[i][j+1])%MOD;
		else								//否则则为偶数 
			for(int j=1;j<=n;j++)
				dp[i][j]=(dp[i-1][j+1]+dp[i][j-1])%MOD;
	}
	return m&1?dp[m][1]:dp[m][n];			//根据m的奇偶性返回最后的答案 
}

int main()
{
	int m,n;
	cin>>m>>n;
	cout<<DP(m,n)<<endl;
	return 0;
} 



9.村庄通电

问题描述
  2015年,全中国实现了户户通电。作为一名电力建设者,小明正在帮助一带一路上的国家通电。
  这一次,小明要帮助 n 个村庄通电,其中 1 号村庄正好可以建立一个发电站,所发的电足够所有村庄使用。
  现在,这 n 个村庄之间都没有电线相连,小明主要要做的是架设电线连接这些村庄,使得所有村庄都直接或间接的与发电站相通。
  小明测量了所有村庄的位置(坐标)和高度,如果要连接两个村庄,小明需要花费两个村庄之间的坐标距离加上高度差的平方,形式化描述为坐标为 (x_1, y_1) 高度为 h_1 的村庄与坐标为 (x_2, y_2) 高度为 h_2 的村庄之间连接的费用为
  sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2))+(h_1-h_2)*(h_1-h_2)。
  在上式中 sqrt 表示取括号内的平方根。请注意括号的位置,高度的计算方式与横纵坐标的计算方式不同。
  由于经费有限,请帮助小明计算他至少要花费多少费用才能使这 n 个村庄都通电。
  
输入格式
  输入的第一行包含一个整数 n ,表示村庄的数量。
  接下来 n 行,每个三个整数 x, y, h,分别表示一个村庄的横、纵坐标和高度,其中第一个村庄可以建立发电站。
输出格式
  输出一行,包含一个实数,四舍五入保留 2 位小数,表示答案。
  
样例输入
4
1 1 3
9 9 7
8 8 6
4 5 4
样例输出
17.41

评测用例规模与约定
  对于 30% 的评测用例,1 <= n <= 10;
  对于 60% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 1000,0 <= x, y, h <= 10000。


分析:题目这意思已经很明显了——求最小生成树。
最小生成树算法主要有两个:prim算法、kruskal算法。如果对这两个算法都不熟悉,建议先学习下再来刷这副本。殿堂级学习通道:【算法与数据结构】—— 最小生成树(Prim算法、Kruskal算法)
学习完上面的文章后,下面直接给出用两个不同算法求解本题的完整代码(含详细注释):

方法一:Prim算法求解

#include
#include
#include
#include
using namespace std;

const int N=1005;
struct Node{
	int x,y,h;
	Node(){	}
	Node(int a,int b,int c)
	{ x=a,y=b,h=c; }
};
struct Edge{
	int x,y;
	float cost;
	Edge(){	}
	Edge(int a,int b,float c)
	{ x=a,y=b,cost=c; }
};
bool cmp(Edge e1,Edge e2)
{ return e1.cost<e2.cost; }
float map[N][N];			//户与户之间的距离产生的费用(由于所有户均互通,因此可以直接用邻接矩阵)
class Prim{
	private:
		int n;				//村庄数
		float cost;			//存放所有户都通电的最小代价 
		Node node[N];		//存放每户的坐标位置
		vector<Edge> e;		//存放在执行insert后插入的边 
		bool vis[N];		//标记某户是否已经通电了
		void create()		//计算map 
		{
			for(int i=1;i<n;i++)
				for(int j=i+1;j<=n;j++)
					map[i][j]=map[j][i]=sqrt(pow(node[i].x-node[j].x,2)+pow(node[i].y-node[j].y,2))+pow(node[i].h-node[j].h,2);
		}
		void insert(int point)
		{
			vis[point]=true;
			for(int i=1;i<=n;i++)
				if(!vis[i])
					e.push_back(Edge(point,i,map[point][i]));
			sort(e.begin(),e.end(),cmp); 
		} 
	public:
		Prim(int num)					//num表示总的村庄个数 
		{
			n=num;
			int x,y,h;
			for(int i=1;i<=n;i++){
				cin>>x>>y>>h;
				node[i]=Node(x,y,h);
			}
			create();
		}
		void run(int start)
		{
			insert(start);
			int selected=1;				//seleced表示当前已经连通了多少户 
			while(selected<n)
			{
				for(int i=0;i<e.size();i++){
					if(!vis[e[i].y]){
						cost+=e[i].cost;
						insert(e[i].y);
						selected++;
						break;
					}
				}
			} 
		}
		float getCost()
		{ return cost; }
};

int main()
{
	int n;cin>>n;
	Prim prim(n);						//构建prim 
	prim.run(1);						//从第1个节点出发 
	printf("%.2f\n",prim.getCost());	//注意格式化输出最终的cost 
	return 0;
} 

方法二:Kruskal算法求解

#include
#include
#include
#include
using namespace std;

const int N=1010;
struct Edge{							//用于表示边的信息
	int x,y;							//x,y分别表示某两户居民
	float cost;							//cost则表示这之间电线的造价 
	Edge(){	}							//无参构造函数
	Edge(int a,int b,float c)			//构造函数 
	{ x=a,y=b,cost=c; }
};
struct Node{
	int x,y,h;
	Node(){	}
	Node(int a,int b,int c)
	{ x=a,y=b,h=c; }
};
bool cmp(Edge e1,Edge e2)				//用于sort函数排序时的规则 
{ return e1.cost<e2.cost; }
class UnionFindSet{						//并查集类,用于连通两个节点以及判断图中的所有节点是否连通 
	private:
		static const int MAX=N;
		int pre[MAX];					//pre数组用于存放某个节点的上级 
		int rank[MAX];					//rank数组用于降低关系树的高度 
		int start,n;					//start和n分别用于指示并查集里节点的起点和终点 
	public:
		void init(int a,int b)			//初始化并查集 
		{
			start=a,n=b;
			for(int i=start;i<=n;i++){
				pre[i]=i;
				rank[i]=1;
			}
		}
		int findPre(int x)				//寻找某个节点的上级 
		{
			if(pre[x]==x) return x;
			return pre[x]=findPre(pre[x]);
		}
		bool isSame(int x,int y)		//判断某两个节点是否连通 
		{ return findPre(x)==findPre(y); }
		bool unite(int x,int y)			//判断两个节是否连通,如果未连通则将其连通并返回真,否则返回假 
		{
			x=findPre(x);
			y=findPre(y);
			if(x==y) return false;
			if(rank[x] > rank[y]) pre[y]=x;
			else{
				if(rank[x]==rank[y]) rank[y]++;
				pre[x]=y;
			}
			return true;
		}
		bool isOne()					//判断整个无向图中的所有节点是否连通 
		{
			int temp=findPre(start);
			for(int i=start+1;i<=n;i++)
				if(findPre(i)!=temp) return false;
			return true;
		}
};
class Kruskal{							//Kruskal算法:求解无向连通图中的最小生成树 
	private:
		int n;							//n表示住户个数 
		static const int MAX=N;
		float cost;						//存放所有户都通电的最小代价 
		Node node[N];					//存放每户的坐标位置
		vector<Edge> e;					//存放录入的无向连通图的所有边 
		UnionFindSet u;					//并查集:抽象实现Graphnew,并完成节点间的连接工作以及判断整个图是否连通 
		void create()					//计算所有住户之间的造价 
		{
			for(int i=1;i<n;i++)
				for(int j=i+1;j<=n;j++)
					e.push_back(Edge(i,j,sqrt(pow(node[i].x-node[j].x,2)+pow(node[i].y-node[j].y,2))+pow(node[i].h-node[j].h,2)));
		}
	public:
		Kruskal(int num)				//num表示总的住户个数 
		{
			n=num;
			int x,y,h;
			for(int i=1;i<=n;i++){
				cin>>x>>y>>h;
				node[i]=Node(x,y,h);
			}
			create();
			sort(e.begin(),e.end(),cmp);//对录入的边进行排序 
			u.init(1,n);				//初始化并查集u 
		}
		void run()						//求解录入的无向连通图的最小生成树 
		{
			for(int i=0;i<e.size();i++){//按序遍历所有边 
				if(u.unite(e[i].x,e[i].y)) cost+=e[i].cost;	//如果当前边能够被接受,则说明这条边属于最小生成树 
				if(u.isOne()) break;	//一旦并查集的连通分支数为1,则说明求出了最小生成树 
			}
		}
		float getCost()
		{ return cost; }
};

int main()
{
	int n;cin>>n;
	Kruskal kruskal(n);					//构建kruskal 
	kruskal.run();						//开始计算 
	printf("%.2f\n",kruskal.getCost());	//注意格式化输出最终的cost 
	return 0;
}



10.种树问题

问题描述
  小明和朋友们一起去郊外植树,他们带了一些在自己实验室精心研究出的小树苗。
  小明和朋友们一共有 n 个人,他们经过精心挑选,在一块空地上每个人挑选了一个适合植树的位置,总共 n 个。他们准备把自己带的树苗都植下去。
  然而,他们遇到了一个困难:有的树苗比较大,而有的位置挨太近,导致两棵树植下去后会撞在一起。
  他们将树看成一个圆,圆心在他们找的位置上。如果两棵树对应的圆相交,这两棵树就不适合同时植下(相切不受影响),称为两棵树冲突。
  小明和朋友们决定先合计合计,只将其中的一部分树植下去,保证没有互相冲突的树。他们同时希望这些树所能覆盖的面积和(圆面积和)最大。
  
输入格式
  输入的第一行包含一个整数 n ,表示人数,即准备植树的位置数。
  接下来 n 行,每行三个整数 x, y, r,表示一棵树在空地上的横、纵坐标和半径。
输出格式
  输出一行包含一个整数,表示在不冲突下可以植树的面积和。由于每棵树的面积都是圆周率的整数倍,请输出答案除以圆周率后的值(应当是一个整数)。
  
样例输入
6
1 1 2
1 4 2
1 7 2
4 1 2
4 4 2
4 7 2
样例输出
12

评测用例规模与约定
  对于 30% 的评测用例,1 <= n <= 10;
  对于 60% 的评测用例,1 <= n <= 20;
  对于所有评测用例,1 <= n <= 30,0 <= x, y <= 1000,1 <= r <= 1000。


分析:仍在研究中……




你可能感兴趣的:(蓝桥杯试题题解)