2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案

前言

本人是二本院校大二的计算机系学生,已经报名了下一届的蓝桥杯省赛,整个寒假学习了很多算法知识,我是看《算法很美》这个课程学习算法的,一套学习下来确实受益匪浅,视频在b站上面都有。

此前已经刷了两套真题,分别为:
2017年第八届蓝桥杯JavaB组省赛 刷题笔记、思路及答案
2016年第七届蓝桥杯JavaB组省赛 刷题笔记、思路及答案

注意:我是以我自身的水平来对题目进行分析的,所以可能代表了多数水平和我相当的朋友的观点,但不代表官方解法(特殊说明除外)。所以如果代码设计题有答案上的错误,还请各位大佬指正哈哈。接下来就来看看这次的第九届蓝桥杯JavaB组省赛的刷题笔记、思路及答案。

刷题笔记

第一题:第几天(已完成)
第二题:方格计数(已完成)
第三题:复数幂(已完成)
第四题:测试次数
第五题:快速排序(已完成)
第六题:递归三元组(已完成)
第七题:螺旋折线(已完成)
第八题:日志统计(已完成)
第九题:全球变暖(已完成)
第十题:堆的计数

第一题:第几天(已完成)

【题目】

2000年的11日,是那一年的第1天。
那么,2000年的54日,是那一年的第几天?


注意:需要提交的是一个整数,不要填写任何多余内容。

【思路】

第一题往往是送分题,这个计算时间的题目可以有很多种解法。

1、简单粗暴法:

2000年是闰年,2月有29天,1月、3月、4月分别是:31天、31天、30天。因此总天数是31+29+31+30+4 = 125天

2、excel快速计算:

我是用excel计算的,excel对于日期时间等功能的计算非常的方便,在A1格子写上末尾日期,然后A2写上初始日期,然后写公式让两个格子相减,即能获得天数。如下图:

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第1张图片
值得注意的是,关于两个数值的相减,一定要检查看看结果有没有多1或少1,为了测试excel减法的情况,我们举个例子:
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第2张图片
很明显,第一天如果是1月1日,那么1月3日应该是第3天,也就是说excel计算出来的答案需要+1,也就是正确答案为125.

3、编程法:

还得写一个for循环,判断日期,判断闰平年,多麻烦,还不如以上两种方法计算,更简单精确。所以这里就不演示编程法了。

【正确答案】

125

第二题:方格计数(已完成)

【题目】

如图p1.png所示,在二维平面上有无数个1x1的小方格。


我们以某个小方格的一个顶点为圆心画一个半径为1000的圆。
你能计算出这个圆里有多少个完整的小方格吗? 

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第3张图片

【思路】

常规做法:

1、首先我们要简化问题,计算包含在圆圈内的格子数目,我们只需要计算以圆心为原点的坐标轴的第一象限(也就是1/4个圆内)的正方形个数即可,然后把算出来的结果×4即可。

2、然后使用两层for循环,遍历1/4圆中的每一个格子(遍历范围=半径×半径),利用勾股定理计算这个格子是否满足在圆内(如下图),也就是判断这个格子离圆心的最远距离L是否小于等于半径R(L=格子右上角那个点到圆心的距离)。如果满足,count++;
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第4张图片
在上图中,红色箭头满足 :2² + 1² <= R² (R=3),因此这个格子在圆内,计数+1;
蓝色箭头不满足 :3² + 1² <= R²(R=3),因此这个格子不在圆内,继续下一次循环。

3、遍历完两层for循环,输出计数count*4即可。

优化做法:

这个做法是我仔细想了之后做出来的能减少for循环次数的方法,基本原理和上面的差不多。

1、还是只看1/4个圆,然后我们可以发现,黑色涂黑部分是一定在圆内的,这一部分可以直接算出来,而不用for循环一个个遍历,省下很多时间。

设黑色区域的边长为x,则一定满足 x² + x² < R²(从图上也能看出来),x < √(R²/2), 这里的√(R²/2)相对比x大一点点,大出来的这一点一定是小数部分,所以去掉小数部分,就是x的值。
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第5张图片
2、随后我们只需遍历绿框所示的部分,大大减少了循环次数。实际上我们只需要计算其中一个绿框的部分然后×2即可。

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第6张图片
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第7张图片
3、count×4,就是最终结果。

【参考答案】

public class test
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int r = sc.nextInt();
		
		//预计算,减少循环次数 r^2 = x^2+x^2 --> x = (int)√(r^2 / 2)
		int x = (int)Math.sqrt((r*r) / 2);	//一定在圆内的正方形格子边长
		int count = x*x;
//			System.out.println(x);//测试
		int y = 1;	//阶数,纵向指针
		int i = x + 1;	//横向指针,用于遍历(绿框内)每个格子
		
		//这个循环只是遍历绿框部分
		while(y <= x)	//i横向(向右)指针,y纵向(向上)指针,当y超过了绿框部分,就跳出循环
		{
			if(i*i + y*y <= r*r)	//关键语句,就算遍历了多余的格子,只要能进入这个判断,就能确定该格子是否在圆内。
			{
				count += 2;	//因为对称性(计算两个绿框部分)
				i ++;	//向右递增
			}
			else	
			{
				y ++;  //向上递增
				i = x + 1;
			}
		}
		System.out.println(count*4);
	}
}

答案经过测试,和网上大佬的答案是相同的。

第三题:复数幂(已完成)

【题目】

设i为虚数单位。对于任意正整数n,(2+3i)^n 的实部和虚部都是整数。
求 (2+3i)^123456 等于多少? 即(2+3i)123456次幂,这个数字很大,要求精确表示。



答案写成 "实部±虚部i" 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。(2+3i)^2 写成: -5+12i,
(2+3i)^5 的写成: 122-597i


注意:需要提交的是一个很庞大的复数,不要填写任何多余内容。

【思路】

首先这道题虽然只是计算123456次方,理论上说使用for循环执行123456也可以做出来,但是由于数据过于庞大,运行肯定会超时。所以需要尝试简化循环次数,所以考虑使用快速幂。如果不懂快速幂,建议百度学习一下,不难。简单来说就是使用递归、二分法来快速计算乘方。

此外,由于数据庞大,所以需要使用BigInteger类,BigInteger类可以表示无限大的整数。

另外计算复数的乘方时,要把虚部和实部分开来算,怎么分开算?其实很简单:

 (a + bi)*(c + di) = ac +adi + bci - bd
=(ac - bd)+(ad + bc)i

两个复数相乘,复数A的实部a,虚部b,复数B的实部c,虚部d,相乘后,得到新的复数C的实部(ac - bd),虚部(ad + bc)。

实部和虚部用一个数组存储即可,这个数组就是代表了这个复数。

【参考答案】

package test;

import java.math.BigInteger;

public class A3
{
	static BigInteger[] ini = {new BigInteger("2"), new BigInteger("3")};
	
	//快速幂,计算a^n
	public static BigInteger[] quick(BigInteger[] a, int n)
	{
		if(n == 1)
			return ini;
		
		if(n % 2 == 0)	//如果n是偶数,返回a^(n/2) * a^(n/2)
			return mul(quick(a, n / 2), quick(a, n / 2));
		else  //如果n是奇数,返回a^(n/2) * a
			return mul(quick(a, n - 1), ini);
	}
	
	//两个复数相乘,返回新的复数
	public static BigInteger[] mul(BigInteger[] a, BigInteger[] b)
	{
//		           shi = a[0] * b[0] - a[1] * b[1]
//					xu = a[0] * b[1] + a[1] * b[0] 
		BigInteger[] t = new BigInteger[2];
		
		t[0] = a[0].multiply(b[0]).subtract(a[1].multiply(b[1]));
		t[1] = a[0].multiply(b[1]).add(a[1].multiply(b[0]));
		return t;
	}
	
	public static void main(String[] args)
	{
		BigInteger[] one = quick(ini, 123456);	//计算ini的123456次方
		System.out.printf("%d%di",one[0],one[1]);
	}
}

代码经过测试,与网上的答案一致。

由于已经提前知道最终结果的实部和虚部都是负数,所以直接打印("%d%di")即可。如果说虚部是正数,才需要打印("%d+%di")。

第四题:测试次数(未完成)

【题目】

x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。

x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。

如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n

为了减少测试次数,从每个厂家抽样3部手机参加测试。

某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?

请填写这个最多测试次数。

注意:需要填写的是一个整数,不要填写任何多余内容。

【思路及答案】

目前还是看不懂题目,建议参考下面大佬的文章。这道题实际上是leetcode里的原题,名字叫“鸡蛋掉落”。等弄懂了题目我再写出思路。

LeetCode 鸡蛋掉落(最清晰的解法)

第五题:快速排序(已完成)

【题目】

以下代码可以从数组a[]中找出第k小的元素。  


它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。


请仔细阅读分析源码,填写划线部分缺失的内容。

import java.util.Random;
public class Main{
	public static int quickSelect(int a[], int l, int r, int k) {
		Random rand = new Random();
		int p = rand.nextInt(r - l + 1) + l;
		int x = a[p];
		int tmp = a[p]; a[p] = a[r]; a[r] = tmp;
		int i = l, j = r;
		while(i < j) {
                	while(i < j && a[i] < x) i++;
                	if(i < j) {
                        	a[j] = a[i];
                        	j--;
                	}
                	while(i < j && a[j] > x) j--;
                	if(i < j) {
                        	a[i] = a[j];
                        	i++;
                	}
        	}
        	a[i] = x;
        	p = i;
        	if(i - l + 1 == k) return a[i];
        	if(i - l + 1 < k) return quickSelect( _________________________________ ); //填空
        	else return quickSelect(a, l, i - 1, k);	
	}
	public static void main(String args[]) {
		int [] a = {1, 4, 2, 8, 5, 7};
		System.out.println(quickSelect(a, 0, 5, 4));
	}
}

【思路及答案】

我写的太长了,就另写了一篇博客来解这道题:
蓝桥杯第九届 javaB组省赛 第五题 快速排序(详解每行代码)

第六题:递归三元组(已完成)

【题目】

给定三个整数数组
A = [A1, A2, ... AN], 
B = [B1, B2, ... BN], 
C = [C1, C2, ... CN],
请你统计有多少个三元组(i, j, k) 满足:

1. 1 <= i, j, k <= N  
2. Ai < Bj < Ck  

【输入格式】
第一行包含一个整数N。
第二行包含N个整数A1, A2, ... AN。
第三行包含N个整数B1, B2, ... BN。
第四行包含N个整数C1, C2, ... CN。

对于30%的数据,1 <= N <= 100  
对于60%的数据,1 <= N <= 1000 
对于100%的数据,1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000 

【输出格式】
一个整数表示答案

【输入样例】
3
1 1 1
2 2 2
3 3 3

【输出样例】
27 

【思路】

这里我用了类似于动态规划的做法。一定要先读懂题目的意思,题目的意思是说,在三行数据中,每行取一个,取出的这三个数字 (Ai,Bj,Ck ) 连起来时应满足 (Ai < Bj < Ck) ,那么这就是一个符合条件的三元组,然后题目要求找出这些三元组的个数。

1、先处理第二、三行:

我们先把第二三行处理,即先找出Bj < Ck的情况。

此时我们主要看第二行的数据,我们的任务是先列出第二行中每个元素的情况数(情况数:当我第二行选择了某个元素时,第三行有多少个元素符合情况)。拿输入样例举例:

1 1 1】
【2 2 2】
【3 3 3

我们先定义一个数组s2,这个数组用于记录第二行的情况数。然后在第二行定义指针p=0, 这个指针是向右扫描的。(以下"[ ]"符号代表该行的指针所在位置)

此时p=0,第二行对应的元素是2,那么当这个元素是2时,第三行有几个元素满足条件呢?可以发现,第三行全部元素都是3,而2 < 3满足条件,所以说,在只看第二、三行的情况下,且p=0时,情况数有3个,然后把这个记录存到刚刚定义的数组s2,即s2[p] = 3

1  1 1】
【[2] 2 2】	p=0		s2【3??】
【 3  3 3

同理,当p=1时,对应的元素是2,在第三行也有3个元素满足条件,因此s2[1] = 3; 再同理,p=2时,s2[2] = 3

1  1 1】
【 2 [2]2】		p=1		s2【33?】
【 3  3 3】

【 1 1  1 】
【 2 2 [2]】	p=2		s2【333】
【 3 3  3

至此,第二行的情况数已记录完成,s2 = 【3,3,3】这个数组代表的意思是,当我第二行选择了其中的第 i 个元素,这个元素后续的情况有s2[i]种。

2、然后处理第一、二行:

处理第一、二行,先定义一个数组s1,用于记录第一行的情况数(和上面的第二行类似)。那么就需要在第一行中定义一个指针p=0(q是第二行指针):

[1] 1 1】  	p=0		s1【???】
【 2  2 2】		q=03  3 3

比如当我第一行选了第一个元素(p=0)时,然后我再在第二行选第1个元素。之前已经计算过,当第二行选择了第1个元素后,后面有3种情况,所以,在第一行是1(p=0)、第二行是2(q=0)的前提下,这种情况有3种。

同理,在第一行是1(p=0)、第二行是2(q=1)的前提,情况有3种。

再同理,在第一行是1(p=0)、第二行是2(q=2)的前提,情况有3种。

综上,第一行是1(p=0)的情况下,一共有3+3+3=9种情况,数组s1[0] = 9;
s1[0] = 9的意思是,当我第一行选了第一个元素,后续情况就有9种。

[1] 1 1】  	p=0		s1【9??】
【 2  2 2】		
【 3  3 3

同理,第一行是1(p=1)的情况下,一共有3+3+3=9种情况,数组s1[1] = 9;

1 [1]1】  	p=1		s1【99?】
【 2  2 2】		
【 3  3 3

再同理,p=2,数组s1[2] = 9;

1 1 [1]】  	p=2		s1【999】
【 2 2  2 】		
【 3 3  3

至此,第一行的情况数已记录完成,s1 = 【9,9,9】这个数组代表的意思是,当我第一行选择了其中的第 i 个元素,这个元素后续的情况有s1[i]种。

3、计算总个数

现在题目的问题就可以简化成:第一行所有元素的情况数之和。

很显然,数组s1的和,就是答案。明白了思路再写代码就容易了。

【参考答案】

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class A6
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		
		int N = sc.nextInt();
		int[][] arr = new int[N][N];
		
		int[] arr1 = new int[N];		//第一行
		for(int i = 0; i < N; i++)
			arr1[i] = sc.nextInt();
		
		int[] arr2 = new int[N];		//第二行
		for(int i = 0; i < N; i++)
			arr2[i] = sc.nextInt();
		
		int[] arr3 = new int[N];		//第三行
		for(int i = 0; i < N; i++)
			arr3[i] = sc.nextInt();
		
		//排序
		Arrays.sort(arr1);
		Arrays.sort(arr2);
		Arrays.sort(arr3);
		
		//上面一堆都是初始化数组,接下来正式进入思路代码
		
//		int[] s1 = new int[N];  //记录第一行每个数字的情况数
		int[] s2 = new int[N];	//记录第二行每个数字的情况数
		int sum1 = 0;	//用于记录s1数组的总和,其实就是最终答案。
		int sum2 = 0;	//用于记录s2数组的总和
		
		int p1 = 0, p2 = 0, p3 = 0;	//三行指针
		
		//先处理第二、第三行
		while(p2 < N && p3 < N)
		{
			/*
				比如:
					【 1,  2, 4, 5】  Ai
					【[3], 4, 6, 9】  Bj
					【2, [5], 7, 9】  Ck  p3=1
				当第二行选择“3”时,在第三行找到第一个比它大的数“5”,记录其位置p3=1
				那么5后续的数都满足条件(前提是数组排好序),因此情况数是N-p3
			*/
			if(arr2[p2] < arr3[p3])		//第二行的起始数小于第三行的起始数(不能等于!题目要求)
			{
				s2[p2] = N - p3;
				sum2 += s2[p2];
				p2 ++;
			}
			else
			{
				p3 ++;
			}
		}
		
		p2 = 0;	//初始化
		//处理第一、二行
		while(p1 < N && p2 < N)
		{
			//第一行的起始数小于第二行的起始数
			if(arr1[p1] < arr2[p2])	
			{
				sum1 += sum2;	//直接相加,可以不用s1数组
				p1 ++;
			}
			else
			{
				/*
				 	sum2本来就是s2的总和,最好的情况就是s2所有元素都能参与,
					比如:	【1,2,3】					【1,     2,    3   】
							【2,3,4】	用s2记录后-->  	【(2,3),(3,2), (4,1)】		-->	  sum2 = 3+2+1=6
							【3,4,5】					(k,v)--> (元素k,该元素的情况数v)
					综上,当第1行是“1”时,第二行所有元素都能参与,情况数就是6

					当第1行是“2”时,第二行就不是全部元素参与了,而会丢掉(2,3)这个元素,
					此时的情况数是2+1,这个和sum2-s2[0](3+2+1 - 3)得到的是一样的,就是减去前缀的意思。
				*/
				sum2 -= s2[p2];	 //减去前缀
				p2 ++;	//指针后移
			}
		}
		
		System.out.println(sum1);
	}
}

第七题:螺旋折线(已完成)

【题目】

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第8张图片

如图p1.pgn所示的螺旋折线经过平面上所有整点恰好一次。  
对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。  

例如dis(0, 1)=3, dis(-2, -1)=9  

给出整点坐标(X, Y),你能计算出dis(X, Y)吗?

【输入格式】
X和Y 

对于40%的数据,-1000 <= X, Y <= 1000  
对于70%的数据,-100000 <= X, Y <= 100000  
对于100%的数据, -1000000000 <= X, Y <= 1000000000  

【输出格式】
输出dis(X, Y)  


【输入样例】
0 1

【输出样例】
3

【思路】

首先化简一下思路,在螺旋的路线中,把图中画 “X” 的线段旋转到下方,使得红色线段可以围成一个正方形。每一层都照这种做法,就会得到很多个嵌套的正方形。每一层正方形的起点和终点都是左下角的坐标(拿第一层举例,原来起始点坐标应该是在 (0,0) ,经过旋转变化后,起始点就到了 (-1,-1)

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第9张图片
举例,如下图所示,目标坐标在(2,2),目标坐标所在层数是2,所以必定要加上第一层的距离(为8)。然后计算第二层的距离:

x轴距离:目标坐标横坐标 - 起始坐标横坐标 = 2 - ( -2 ) = 4
y轴距离:目标坐标纵坐标 - 起始坐标纵坐标 = 2 - ( -2 ) = 4

横纵坐标差之和 = x 轴距离 + y 轴距离

所以总距离 = 第一层距离数 + 横纵坐标差之和 = 16, 也就是蓝色箭头个数之和。
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第10张图片
但是很显然,上面计算距离的公式是不完整的。比如下面图示的情况,当目标坐标为(2,0) 时,上面公式就得不出正确答案。
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第11张图片
因此我们要换一种思路,即反向计算(绿色箭头)。仍然先计算目标坐标与起始坐标的横纵坐标差之和

横坐标之差 = 2 - ( -2 ) = 4
纵坐标之差 = 0 - ( -2 ) = 2
横纵坐标差之和 = 6

这里计算的横纵坐标差之和绿色箭头的距离,所以总距离 = 第二层距离数 - 横纵坐标差之和 = 24 - 6 = 18. 这里要注意【第二层距离数】包括了【第一层距离数】

【参考答案】

package _9s_LanQiao;

import java.util.Scanner;

public class A7
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int X = sc.nextInt();
		int Y = sc.nextInt();
		
		int n = Math.max(Math.abs(X), Math.abs(Y));	//该点所在的层数
		/*
		 * 第1层 = 2*4 = 8
		 * 第1+2层 = 2*4 + 4*4 = 24
		 * 第1+2+3层 = 2*4 + 4*4 + 6*4 = 48
		 * 第1+...+n-1层 = 8*(1+2+..+n-1) = 4n(n-1)
		 * 第1+...+n层 = 8*(1+2+..+n) = 4n(n+1)
		 */
		long sum_n = 4 * n * (n + 1);		 //第n层完整正方形
		long sum_n_1 = 4 * n * (n - 1);	 //第n-1层完整正方形
		long sum = 0;	//总距离
		
		//先计算【目标坐标与起始坐标】的横纵坐标差,计算总距离(横纵坐标差之和):
		int x_x = X - (-n);
		int y_y = Y - (-n);
		int xy = x_x + y_y;	 //总距离(横纵坐标差之和)
		
		//如果目标坐标落在了正方形左侧、上侧:
		if((-n <= Y && Y <= n && X == -n) || (-n <= X && X <= n && Y == n))
			sum = sum_n_1 + xy;
		
		//如果目标坐标落在了正方形右侧、下侧:
		if((-n <= Y && Y <= n && X == n) || (-n <= X && X <= n && Y == -n))
			sum = sum_n - xy;
		
		System.out.println(sum);
	}
}

时间复杂度O(1),代码可以更简洁。

第八题:日志统计(已完成)

【题目】

小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。其中每一行的格式是:

ts id  

表示在ts时刻编号id的帖子收到一个"赞"。  

现在小明想统计有哪些帖子曾经是"热帖"。如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。  

具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。  

给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。  

【输入格式】
第一行包含三个整数N、D和K。  
以下N行每行一条日志,包含两个整数ts和id。  

对于50%的数据,1 <= K <= N <= 1000  
对于100%的数据,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000  

【输出格式】
按从小到大的顺序输出热帖id。每个id一行。  

【输入样例】
7 10 2  
0 1  
0 10    
10 10  
10 1  
9 1
100 3  
100 3  

【输出样例】
1  
3  

【思路】

1、读题:

在输入样例中,输入的格式是【时间点ts+帖子号id】,这一行表示帖子号为id的帖子在时间点ts收获了一个赞

然后输入的【参数D】是指一段时间,【参数K】是指在这段时间D内,如果点赞数不少于K,则这个帖子是热帖

用图片讲述一下题目所表达的意思:
2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第12张图片
图中数轴的值(黑色数字)代表时间点ts,红色数字代表帖子的id。所以数值上每个点都表示了该帖子在该时间点收获了一个点赞。那么如何判断帖子是否是热帖?那就看一个时间段D内,该帖子是否被点赞了K次。我们假设K = 3,D = 4

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第13张图片
假设这个时间段D的起点和数轴的起点相同,那么这个时间段D中,帖子1被点赞了1次,帖子2被点赞了2次。(不包括帖子3,因为题目说了这个时间段区间是左闭右开区间),由于没有一个帖子点赞数大于等于K(K=3),所以这个时间段内没有热帖。

按照题目的意思,这个时间段是可以任意移动的,比如我可以移动到如下图:

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第14张图片
在这个时间段中,帖子2被点赞了2次,帖子3被点赞了1次。

然后我们把时间段移动到一个比较巧妙的位置:

2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案_第15张图片
这种情况,很明显帖子2被点赞了3次,满足热帖的条件(因为K=3),因此,帖子2是热帖。

2、代码实现

根据上面的思路,我们只需要得到某个帖子所有的时间点,然后用时间段一个个去比较,如果时间段内存在K个及以上的时间点,那这个帖子就是热帖了。

我还是参考了一下网上大佬的代码才做出来的。当我看到下面这段代码时,我就知道应该怎么做了:

Map<Integer, List<Integer>> map = new HashMap<>();

创建map集合,key是帖子的id,value是一个list集合,用于存放该帖子id的所有时间点。

【参考答案】

package _9s_LanQiao;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

/*
 * 
第一行包含三个整数N、D和K。  	N帖子数,D时间段,K热帖点赞不少于该数

【输入样例】
ts id
7 10 2  
0 1  
0 10    
10 10  
10 1  
9 1
100 3  
100 3  

【输出样例】
1  
3  

 */
public class A8 
{
	public static void main(String[] args) 
	{

		Map<Integer, List<Integer>> map = new HashMap<>();
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int D = sc.nextInt();
		int K = sc.nextInt();
		
		for(int i = 0; i < N; i++)
		{
			int ts = sc.nextInt();	//时间点
			int id = sc.nextInt();	//帖子号
			List<Integer> list = new ArrayList<>();
			
			if(!map.containsKey(id))	//如果hashmap没有这个id
			{
				list.add(ts);
			}
			else	//如果有这个id,拿出列表,添加值进去
			{
				list = map.get(id);
				list.add(ts);
			}
			map.put(id, list);
		}

		for(Map.Entry<Integer, List<Integer>> entry : map.entrySet())	//为了拿到key和value
		{
			//entry.getKey()可拿到键k,entry.getValue()可拿到值v
			List<Integer> list = entry.getValue();
			Collections.sort(list);	//拿出这个帖子的所有时间点,排序
			for(int i = 0; i < list.size() - K + 1; i++)
			{
				//拿时间段D去比较,就是用上面提到的思路,满足条件就输出id即可。
				//这里我的思路是:如果从第i个时间点开始,往下的第K-1个时间点与第i个时间点之差 如果小于时间段D,那这个帖子就是热帖。
				//也就是说时间段D内存在大于等于K个的时间点,那这个帖子就是热帖。
				if(list.get(i + K - 1) - list.get(i) < D)			//[0,9]	D=10, K=2
				{
					System.out.println(entry.getKey());
					break;
				}
			}
        }
	}
}

第九题:全球变暖(已完成)

【题目】

你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:

.......
.##....
.##....
....##.
..####.
...###.
.......

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。  

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。  

例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。  

【输入格式】
第一行包含一个整数N。  (1 <= N <= 1000)  
以下N行N列代表一张海域照片。  

照片保证第1行、第1列、第N行、第N列的像素都是海洋。  

【输出格式】
一个整数表示答案。

【输入样例】
7 
.......
.##....
.##....
....##.
..####.
...###.
.......  


【输出样例】
1  

【思路】

先理解一下题目的意思(我之前由于没有理解题目意思,思路和代码全错了,所以看懂题很重要!!!):

‘ . ’是海洋,’ # ‘是陆地。题目给定一个岛屿图,经过变化后,只要“陆地”的上下左右有一个是“海洋”,那么这块“陆地”就会被淹没(变成’ . ')

判断一块区域是不是岛屿:“陆地”的上下左右连在一起的一片陆地就是一块岛屿。

随后通过dfs,把相连的“陆地”都标记为“1”,把不会淹没的“陆地”标记为“2”,由于进行一次dfs查找的范围就是一块岛屿,所以如果这块岛屿出现了“2”标记,说明这块岛屿还存在,没有被完全淹没。

【参考答案】

import java.util.Scanner;

/*
 *  
7
.......
.##....
.##....
....##.
..####.
...###.
.......

.......
.......
.......
.......
....#..
.......
.......


 */

public class A9
{
	//定义方向,上右下左
	static int[][] d = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
	static int x = 0, y = 1;		//只是为了方便直观
	static boolean stay = false;	//这块岛屿是否能留下来?先初始化为不能留下来。 
	static int count = 0;			//计算有多少个岛屿沦陷了
	
	//打印地图,方便测试
	public static void print(int[][]map)
	{
		for(int i = 0; i < map.length; i++)
		{
			for(int j = 0; j < map[0].length; j++)
			{
				System.out.print(map[i][j]);
			}
			System.out.println();
		}
		
	}
	
	public static void dfs(char[][] map, int[] node, int[][] book)
	{
		//只要进来dfs了,坐标node就一定是陆地,把他标记成1
		book[node[x]][node[y]] = 1;
		//定义flag,初始化为true,意思是默认当前的node的上下左右都是“陆地”。
		boolean flag = true;
		//四个方向走一遍
		for(int i = 0; i < 4; i++)
		{
			//下一个结点
			int[] next = new int[] {node[x] + d[i][x], node[y] + d[i][y]};
			//如果这个方向的像素仍然是陆地且dfs没有走过
			if(map[next[x]][next[y]] == '#' && book[next[x]][next[y]] == 0)
				dfs(map, next, book);	//下一节点进行dfs
			
			if(map[next[x]][next[y]] == '.')
				flag = false;	//只要node有一个方向有水,它就留不下来,flag记为false
		}
		
		//如果flag是true,说明这个节点node上下左右都是陆地,那它就不会被淹没,记为2
		if(flag)
		{
			book[node[x]][node[y]] = 2;
			stay = true;	//这块岛屿留下来了。
		}
	}
	
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();		//地图边长
		String[] t = new String[n];
		
		Scanner sc2 = new Scanner(System.in);
		for(int i = 0; i < t.length; i++)
			t[i] = sc2.nextLine();
		
		char[][] is = new char[n][n];	//岛屿平面地图
		
		for(int i = 0; i < n; i++)
		{
			char[] temp = t[i].toCharArray();
			for(int j = 0; j < n; j++)
				is[i][j] = temp[j];
		}
		
		//上面一大串都是为了得到地图的二维数组,下面开始正式dfs
		int[][] book = new int[n][n]; //标记数组
		
		for(int i = 0; i < n; i++)
		{
			for(int j = 0; j < n; j++)
			{
				if(is[i][j] == '#' && book[i][j] == 0)	//如果发现了一块"新"陆地,且这个地方没有走过(没有标记成1或2)
				{
					stay = false;	//初始化
					int[] node = new int[] {i, j};	//就把当前节点(坐标)传入dfs
					dfs(is, node, book);
					if(!stay)	//如果岛屿没有留下来,计数
						count ++;
				}
			}
		}
//		print(book);
		System.out.println(count);
	}
}

第十题:堆的计数(待完成)

【题目】


标题:堆的计数

我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。  
每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。  

假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?  

例如对于N=4有如下3种:

    1
   / \
  2   3
 /
4

    1
   / \
  3   2
 /
4

    1
   / \
  2   4
 /
3

由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。  


【输入格式】
一个整数N。  
对于40%的数据,1 <= N <= 1000  
对于70%的数据,1 <= N <= 10000  
对于100%的数据,1 <= N <= 100000

【输出格式】
一个整数表示答案。  

【输入样例】
4  

【输出样例】
3

【思路及答案】

我看网上大佬的答案,用到了逆元、动态规划等,太难了……还是等到会做了再写吧。

你可能感兴趣的:(2018年第九届蓝桥杯 JavaB组省赛 刷题思路及答案)