2020年蓝桥杯第一次校内模拟赛题解与笔记

题解笔记正文

1.单位换算

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

答案:15448
解析:单位换算乘上1024即可


2.正约数

问题描述
  1200000有多少个约数(只计算正约数)。
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:96
解析:这种规模直接暴力求解


public class num2 {

	public static void main(String[] args){
		int cnt=0;
		for(int i=1;i<=1200000;i++){
			if(1200000%i==0) cnt++;
		}
		System.out.print(cnt);
	}
}


3.数字9

问题描述
  在1至2019中,有多少个数的数位中包含数字9?
  注意,有的数中的数位中包含多个9,这个数只算一次。例如,1999这个数包含数字9,在计算只是算一个数。
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:544
解析:仍然无脑暴力


public class num3 {
	public static void main(String[] args){
		int cnt=0;
		for(int i=1;i<=2019;i++){
			if(check9(i)) cnt++;
		}
		System.out.print(cnt);
	}
	
	static boolean check9(int num){
		while(num!=0){
			if(num%10==9) return true;
			else num/=10;
		}
		return false;
	}
}


4.最多叶结点

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

答案:1010
解析:首先要确定是二叉树,多叉树最多可以为2018个结点。二叉树有个性质是: 叶结点数 = 度为2的结点数+1 ,很明显完全二叉树的度为2的结点数最多,则二叉树有式子: 结点总数= 叶子结点数 + N1结点数 + N2结点数
当N1结点数为0,联立二式可得 叶结点数等于1010。
(还有更快的方法,自己推推看。因没考虑到奇偶问题,导致错算成1009)


5.单词分段

问题描述
  小明对类似于 hello 这种单词非常感兴趣,这种单词可以正好分为四段,第一段由一个或多个辅音字母组成,第二段由一个或多个元音字母组成,第三段由一个或多个辅音字母组成,第四段由一个或多个元音字母组成。
  给定一个单词,请判断这个单词是否也是这种单词,如果是请输出yes,否则请输出no。
  元音字母包括 a, e, i, o, u,共五个,其他均为辅音字母。
输入格式
  输入一行,包含一个单词,单词中只包含小写英文字母。
输出格式
  输出答案,或者为yes,或者为no。

解析:典型字符串模式匹配,有请正则表达式:

[aeiou]匹配这些字符的任一一个,+代表可出现1至多次,而?代表0-1次
[^aeiou]匹配非该字符的任一一个,理论上还需将其限定在英文字母范围内,题目保证出现小写字母,遂可不加。
()就是普通的括号,强调优先级。
{2}指的是将前面的模式匹配两次的意思(在这里我将前面模式用括号括住)

看到这里有没有觉得[ ] 和 { } 的用法跟软件工程数据字典数据表示方式里面的符号功能差不多?!(but括号内部含义不一样)

Java的正则用两个类表示,Pattern是regex的内容,Matcher 用来匹配。

import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class num5 {
	public static void main(String[] args){
		int cnt=0;
		Scanner sc=new Scanner(System.in);
		String thisLine=sc.next();
		Pattern pattern=Pattern.compile("(([^aeiou]+)([aeiou]+)){2}");
		 Matcher matcher = pattern.matcher(thisLine);
		if(matcher.matches()){
			System.out.println("yes");
		}
		else{
			System.out.println("no");
		}
	}
}

感谢 BD梦旅人 指正 :应使用完全匹配。


6 .数位递增

问题描述
  一个正整数如果任何一个数位不大于右边相邻的数位,则称为一个数位递增的数,例如1135是一个数位递增的数,而1024不是一个数位递增的数。
  给定正整数 n,请问在整数 1 至 n 中有多少个数位递增的数?
输入格式
  输入的第一行包含一个整数 n。
输出格式
  输出一行包含一个整数,表示答案。
样例输入
30
样例输出
26
评测用例规模与约定
  对于 40% 的评测用例,1 <= n <= 1000。
  对于 80% 的评测用例,1 <= n <= 100000。
  对于所有评测用例,1 <= n <= 1000000。

答案:由于数据规模并不大,因此可以直接暴力枚举

import java.util.Scanner;


public class num6 {
	public static void main(String[] args){
		int cnt=0;
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		
		for(int i=1;i<=n;i++){
			if(check(i))	cnt++;
		}
		System.out.print(cnt);
		
	}
	
	static boolean check(int n){
		int t=n%10;   //右边数
		int bet=0;     //左边数
		while(n!=0){    //从右往左遍历
			n/=10;
			bet=n%10;
			if(bet>t) return false;
			t=bet;
		}
		return true;
	}
}


7.递增三元组

问题描述
  在数列 a[1], a[2], …, a[n] 中,如果对于下标 i, j, k 满足 0<i<j<k<n+1 且 a[i]<a[j]<a[k],则称 a[i], a[j], a[k] 为一组递增三元组,a[j]为递增三元组的中心。
  给定一个数列,请问数列中有多少个元素可能是递增三元组的中心。
输入格式
  输入的第一行包含一个整数 n。
  第二行包含 n 个整数 a[1], a[2], …, a[n],相邻的整数间用空格分隔,表示给定的数列。
输出格式
  输出一行包含一个整数,表示答案。
样例输入
5
1 2 5 3 5
样例输出
2
样例说明
  a[2] 和 a[4] 可能是三元组的中心。
评测用例规模与约定
  对于 50% 的评测用例,2 <= n <= 100,0 <= 数列中的数 <= 1000。
  对于所有评测用例,2 <= n <= 1000,0 <= 数列中的数 <= 10000。

答案:可以直接动规。这里我们可以发现递增三元组的第一数有个特点,后面必然有两个大于它的数,那么我们求出每一项的递增子序列个数,只需要统计递增子序列个数至少为2的项即可。时间复杂度为O(N^2)。此方法
时间复杂度可以进一步优化到O(NlogN)

import java.util.Scanner;

public class num7 {
	static int[][] a=new int[101][2];
	public static void main(String[] args){
		int cnt=0;
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		for(int i=1;i<=n;i++){
			a[i][0]=sc.nextInt();
		}
		
		for(int i=n-1;i>=1;i--){
			for(int j=i+1;j<=n;j++){
				if(a[i][0] < a[j][0])  a[i][1]=Math.max(a[i][1], a[j][1]+1);
			}
		}
		
		for(int s=1;s<=n;s++){
			if(a[s][1]>1) cnt++;
		}
		System.out.print(cnt);
	}
}


8.草地

问题描述
  小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
  小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。
  这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为有草的小块。
  请告诉小明,k 个月后空地上哪些地方有草。
输入格式
  输入的第一行包含两个整数 n, m。
  接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
  接下来包含一个整数 k。
输出格式
  输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。
样例输入:(网上很多样例是错误的,我自己手动输入了一遍)
4 5
.g…

…g.

2

样例输出:
gggg.
ggggg
.gggg
…ggg
评测用例规模与约定
  对于 30% 的评测用例,2 <= n, m <= 20。
  对于 70% 的评测用例,2 <= n, m <= 100。
  对于所有评测用例,2 <= n, m <= 1000,1 <= k <= 1000。
  
答案:经典bfs,实现没什么好说的。思路是共计k个月,每个月刷新一次,每次把’g’向外拓展一格即可。
其中map存放草地

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
//G为队列存储的点,包含步数和坐标
class G{
	int step,x,y;

	public G(int step, int x, int y) {
		super();
		this.step = step;
		this.x = x;
		this.y = y;
	}
}

public class 草地 {

	static int n;
	static int m;
	static int k;
	static char[][] mp;
	static Queue<G> qn=new LinkedList<G>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	
		
		Scanner sc=new Scanner(System.in);

		n=sc.nextInt();
		m=sc.nextInt();
		
		
		mp=new char[n][m];
		for (int i = 0; i < n; ++i) {
			mp[i]=sc.next().toCharArray();
		}
		k=sc.nextInt();
		
		
		for (int i = 0; i < n; ++i) 
			for (int j = 0; j < m; ++j) {
				if(mp[i][j]=='g')
					qn.offer(new G(0,i,j));
		}

		bfs();
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < m; ++j) {
				
				System.out.print(mp[i][j]);
			}
			System.out.println();
		}
	}
	
	static void bfs(){
		while(!qn.isEmpty()){
			G g=qn.peek();
			if(g.step==k){
				return;
			}
			
			if( g.x-1>=0 &&  mp[g.x-1][g.y]=='.' ){
				mp[g.x-1][g.y]='g';
				qn.offer(new G(g.step+1,g.x-1,g.y));
			}
			if( g.x+1<n &&  mp[g.x+1][g.y]=='.' ){
				mp[g.x+1][g.y]='g';
				qn.offer(new G(g.step+1,g.x+1,g.y));
			}
			if( g.y-1>=0 &&  mp[g.x][g.y-1]=='.' ){
				mp[g.x][g.y-1]='g';
				qn.offer(new G(g.step+1,g.x,g.y-1));
			}
			if( g.y+1<m &&  mp[g.x][g.y+1]=='.' ){
				mp[g.x][g.y+1]='g';
				qn.offer(new G(g.step+1,g.x,g.y+1));
			}
			qn.poll();
		}
	}

}


9.正整数序列

问题描述
  小明想知道,满足以下条件的正整数序列的数量:
  1. 第一项为 n;
  2. 第二项不超过 n;
  3. 从第三项开始,每一项小于前两项的差的绝对值。
  请计算,对于给定的 n,有多少种满足条件的序列。
输入格式
  输入一行包含一个整数 n。
输出格式
  输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
4
样例输出
7
样例说明
  以下是满足条件的序列:
  4 1
  4 1 1
  4 1 2
  4 2
  4 2 1
  4 3
  4 4
评测用例规模与约定
  对于 20% 的评测用例,1 <= n <= 5;
  对于 50% 的评测用例,1 <= n <= 10;
  对于 80% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 1000。
  
解析:写的过程中漏分析情况,一开始我列出n为1到5的情况,发现n i等于 (n-1) i与 (n-2) i的数量之和,于是直接递推,从分析、编码到提交不到8分钟。顺利的根本不像第九题,当时时间非常充裕,遂打算做完第十题回来检查。

下面附上正确的完整分析以及两份代码。
由“从第三项开始,每一项小于前两项的差的绝对值”,是明显得递归定义(不好看出来请补递归知识),采用递归算法,时间复杂度就递归树而言应为指数级,O(2^n)总是重复运算

import java.util.Scanner;

public class num9{
	static int ans=0;
	static int n=0;
	final static int MOD=10000;
	public static void main(String[] args){
		 n=new Scanner(System.in).nextInt();
		for(int i=1;i<=n;i++){
			rec(n,i);
		}
		System.out.print(ans)}
	
	static void rec(int first,int second){
		for(int i=1;i<Math.abs(first-second);i++){
			rec(second,i);
		}
		ans=(ans+1)%MOD;
	}
}


优化

添加备忘录的记忆化搜索,携带天生“剪枝”的搜索,旨在提供一个简单的二维ans表单,在递归过程中遇到重复值时,首先查表而非计算。

我们可以将memoized_search的调用分为两类
1.调用ans[first][second]!=0,因此返回表单数据
2.调用ans[first][second]==0,因此执行以下计算程序

循环O(n)次,每个表项一次,递归调用均为循环所产生的调用开销,最坏情况下遍历二维数组所有位置有O(n^ 2)次,每次调用花费O(1)的时间,循环带来递归调用的开销,算法总时间为O(n^3)。记忆化搜索将算法从指数级优化到 O(n ^3),空间复杂度达到O(n ^2),是典型的时空互换。

通常情况下,如果每个子问题必须求解一次,那么自底向上的动态规划会比自顶向下的记忆化搜索快一个常数系数(因为记忆化搜索有递归额外开销),维护表的开销减少了。对于某些问题,还可以优化表的访问模式来进一步降低时空代价。相反,如果只需要求特殊子问题,记忆化搜索的优势就提醒出来了,因为它只会求解绝对必要的解。

(查阅网上资料后,这道题用纯动态规划似乎存在一定的问题)
记忆化搜索:

import java.util.Scanner;


public class num9{
	static int res=0; 
	static int n=0;
	final static int MOD=10000;
	public static void main(String[] args){
		 n=new Scanner(System.in).nextInt();
		int[][] ans=new int[n+1][n+1];
		
		for(int i=1;i<=n;i++){
			res+=memoized_search(ans,n,i);
		}
		
		System.out.print(res%MOD);
	}

	static int memoized_search(int[][] ans,int first,int second){  //记忆化
		if(ans[first][second]!=0)
			return ans[first][second];
		
		int t=1;      // n n 的情况,故初始就有一种
		for(int i=1;i<Math.abs(first-second);i++){
			t=(t+memoized_search(ans,second,i))%MOD;
		}
		return ans[first][second]=t%MOD;
	}
}

备注:到此依然不能过100%的检查点。 大佬可以dp一下


10.晚会节目

问题描述
  小明要组织一台晚会,总共准备了 n 个节目。然后晚会的时间有限,他只能最终选择其中的 m 个节目。
  这 n 个节目是按照小明设想的顺序给定的,顺序不能改变。
  小明发现,观众对于晚上的喜欢程度与前几个节目的好看程度有非常大的关系,他希望选出的第一个节目尽可能好看,在此前提下希望第二个节目尽可能好看,依次类推。
  小明给每个节目定义了一个好看值,请你帮助小明选择出 m 个节目,满足他的要求。
输入格式
  输入的第一行包含两个整数 n, m ,表示节目的数量和要选择的数量。
  第二行包含 n 个整数,依次为每个节目的好看值。
输出格式
  输出一行包含 m 个整数,为选出的节目的好看值。
样例输入
5 3
3 1 2 5 4
样例输出
3 5 4
样例说明
  选择了第1, 4, 5个节目。
评测用例规模与约定
  对于 30% 的评测用例,1 <= n <= 20;
  对于 60% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 100000,0 <= 节目的好看值 <= 100000。

答案:
直接两个多关键词快排即可啦。

刚开始读题还以为是奇数个好看,偶数个比前一项奇数更好看,换句话说第一项好看,第二项更好看,第三项好看,第四项更好看,这样依次渐进。
于是我用动规和贪心找出了这样的情况,测试数据的时候,总觉得理解的题意哪里不太对,细品“尽可能”,才恍然大悟题目意思理解错了。

然后想着用两次多关键词快排去解决,写完一个快排调试了许久,慌乱之中没有用指针作为函数列表形参封装,导致还得写一个,
就这样来来回回一道题两个致命错误结束了比赛。。

下面是赛后写完的:在这里我用一个多关键词快排,再将乱序输出值按升序输出(用自带的sort)

import java.util.Arrays;
import java.util.Scanner;


public class num10 {
	static int[] a=new int[101];
	static int[][] b=new int[101][2];
	static int n,m;
	public static void main(String[] args){
		Scanner sc=new Scanner(System.in);
		 n=sc.nextInt();
		 m=sc.nextInt();
		for(int i=1;i<=n;i++){
			b[i][0]=sc.nextInt();
			b[i][1]=i;
		}
		
		qsort(1,n);
		int cnt=1;
	
		for(int i=n;i>=1;i--){
			if(cnt<=m){
//				a[cnt][1]=b[i][0];
				a[cnt++]=b[i][1];
			}
		}
		cnt=0;
		Arrays.sort(a);
		int j=0;
		while(j<m  ){
			if(a[cnt]>0){
				System.out.print(a[cnt]);
				j++;
			}
			cnt++;
		}
	}
	
	static void qsort(int x,int y){
		int i=x , j=y ,mid1=b[(x+y)/2][0] , mid2=b[(x+y)/2][1], t=0;
		while(i<=j){
			while((b[i][0]<mid1)|| ((b[i][0]==mid1)&& (b[i][1]<mid2))) i++;
			while((b[j][0]>mid1)|| ((b[j][0]==mid1)&& (b[j][1]>mid2))) j--;
			if(i<=j){
				t=b[i][0]; b[i][0]=b[j][0]; b[j][0]=t;
				t=b[i][1]; b[i][1]=b[j][1]; b[j][1]=t;
				i++;j--;
			}
		}
		if(x<j) qsort(x,j);
		if(i<y) qsort(i,y);
	}
}

Java与c/c++在竞赛编程的异同

(竞赛编程中)Java最吸引我的两个特性之一在于编译器对代码有着严格的安全性检查。

一般来说得经常考虑 初始化问题、是否越界、数组&容器长度的存储。

Java中引用变量初始化为NULL,而容器(自动分配初始空间)、数组、类变量都默认自动初始化,局部变量则必须进行人为初始化才可使用。

Java能对越界情况投出有效的异常对象,这得益于Java内置的异常处理机制,其是Java在众多语言中独树一帜的因素之一。它以额外的内存开销和每次运算对下标进行检查为代价。

在需要时,c/c++通常把数组长度置于首位,而Java则将长度包装成类的成员变量,这使得数组的遍历以及高精度表示等方便了许多。

关于遍历数组与容器。foreach语句虽然只需要知道参数类型、引用变量即可低成本只读式遍历数组、容器等,尽管可以引入range对象限定遍历范围(在Python里也有类似用法,只是将其为函数),但个人不建议将foreach语句用在竞赛编程中,因为它失去了对下标的掌控。

Java在括号逻辑表达式中只允许boolean两种形式存在;c/c++可以自动将整数转成布尔值 ,可能带来一些隐蔽的错误。

scope:c/c++将花括号范围定为作用域,且可以嵌套;而Java认为自由嵌套会产生混乱,故取消这一特性,另一种替代方案之一来自于内部类。

命名空间:c/c++采用命名空间;Java则引入包的概念,因为“包治百病”嘛。

c++对效率的极致追求,是以失去一些良好的特性为代价的。比如它放弃了单根继承结构,c++面临与c向后兼容的问题。再比如它需要c++程序员人工的及时释放堆内存空间。堆内存虽相较堆栈内存灵活性高,但系统无法掌握分配、销毁内存的时机,而java自带垃圾回收机制可以在牺牲一定性能的基础上解决这一问题。

java作为一门全新的纯粹的面向对象编程语言,主打移植性以及降低程序员的负担,在早期推广的演讲中曾被称为c++ --,意为去除了c++繁琐的部分。即使前面列举了c++的种种不足,就个人而言c++依然是令人向往的语言,c++的效率是java20-50倍,从编译到运行的速度就可以初步看出来了。java有着极速的编译器,能将编译速度优化到接近c++,代价是牺牲了移植性。

发布了23 篇原创文章 · 获赞 10 · 访问量 4982

猜你喜欢

转载自blog.csdn.net/Fzidx/article/details/105018813