蓝桥杯2018年省赛[第七届]-JavaB组赛题解析
真题文档:https://www.lanqiao.cn/courses/2786/learning/?id=70876
由于篇幅原因,本篇只有6-10题(所有编程题)的解析,1-5题解析见上篇文章
蓝桥杯2019年省赛[第十届]-JavaB组赛题解析(上)
F.特别数的和
1.原试题
2.简要分析
- 题很简单,数据也不大,只要一个个枚举就可以通过了。判断数字中是否含某个字符,转成字符串进行判断就行了。
- 签到编程题?
3.实现代码
public class _F特别数的和 {
static int n;
static long ans;
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
solve();
}
static void solve(){
for(int i=1;i<=n;i++){
if(check(i)){
ans+=i;
}
}
System.out.println(ans);
}
static boolean check(int a){
return String.valueOf(a).indexOf('2')!=-1 ||
String.valueOf(a).indexOf('0')!=-1 ||
String.valueOf(a).indexOf('1')!=-1 ||
String.valueOf(a).indexOf('9')!=-1;
}
}
G.外卖店优先级
1.原试题
- 输入样例:
2 6 6
1 1
5 2
3 1
6 2
2 1
6 2
- 输出样例:
1
2.简要分析
-
一看,是个模拟类的题,我们先按照题目的要求一步步的去模拟。需要注意的点是:
- 最小减少到0。
- 第一个时间点不一定从1开始,最后一个时间点也不一定达到了t。
- 要考虑到进入优先缓存中后掉下来的情况。
- 要考虑到同一个时间点同一个店家有多个订单的情况。
-
综合上述的注意事项,我们可以做出基础的模拟过程。
- 先按照时间,编号对这些日志进行排序。
- 计算出当前时间点与上一个时间点的间隔。
- 统计当前时间点有哪些店家是有订单的。
- 对有订单的优先级+2,否则-1。
- 每过完一个时间点就检查一遍所有店家的优先级,考虑是否还在优先缓存中。
- 最后统计末尾时刻还在优先缓存中店家的数目。
-
上述的模拟完成了题目的要求,但是只能拿到60%的分数,因为在这种做法里面,每一个时间点都需要去更新并检查所有时间点的状态,只要店家数目一多,就会超时,所以我们需要对这种模拟进行优化。
-
我们发现,主要浪费时间的是,很多店家很长一段时间可能都没有订单,却还需要更新检查他们的状态。我们可以使用一个last数组,来记录每一个店家上一次有订单时的时间,这样,我们每一个时间点,只需要操作有相关日志的店家即可,其他店家就不需要操作了,这样的时间复杂度基本上是达到了
O(N)
的。
3.实现代码1(普通模拟)(60%分数)
public class _G外卖店的优先级 {
static int n;
static int m;
static int t;
static Log[] logs;
static int[] a;//动态存储每个id对应的优先级情况
static boolean[] isCache;//某个时刻某个id是否在缓存中
static class Log implements Comparable<Log>{
int ts;
int id;
public Log(int ts, int id) {
this.ts = ts;
this.id = id;
}
@Override
public int compareTo(Log o) {
if(ts==o.ts){
return id-o.id;
}else{
return ts-o.ts;
}
}
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
t=sc.nextInt();
logs=new Log[m];
a=new int[n+1];
isCache=new boolean[n+1];
for(int i=0;i<m;i++){
logs[i]=new Log(sc.nextInt(),sc.nextInt());
}
solve0();
}
static void solve(){
//日志按照时间进行排序
Arrays.sort(logs);
for(int i=0;i<m;i++){
int j=i+1;
for(;j<m;j++){
if(logs[j].ts!=logs[i].ts){
break;
}
}
//此时[i,j-1]区间内所有的ts是相同的
}
}
static void solve0(){
//日志按照时间进行排序
Arrays.sort(logs);
int interval=0;//时间间隔
for(int i=0;i<m;){
int j=i+1;
for(;j<m;j++){
if(logs[j].ts!=logs[i].ts){
break;
}
}
//此时[i,j-1]区间内所有的ts是相同的
//计算出当前ts与之前的时间间隔
if(i==0){
interval=logs[i].ts;
}else{
interval=logs[i].ts-logs[i-1].ts;
}
//q数组存储的是当前时间有订单的商家id
int q[]=new int[j-i];
int index=0;
for(int k=i;k<j;k++){
q[index++]=logs[k].id;
}
aupdate(interval,q);
acheck();
i=j;
}
//最后一次时间间隔也需要计算
int final_sub=t-logs[m-1].ts;
aupdate(final_sub,new int[0]);
acheck();
int ans=0;
//统计在优先缓存中的数目
for(int i=1;i<=n;i++){
if(isCache[i]){
ans++;
}
}
System.out.println(ans);
}
//检查所有店家的优先级是在优先缓存中
static void acheck(){
for(int i=1;i<=n;i++){
if(a[i]>5){
isCache[i]=true;
}else if(a[i]<=3){
isCache[i]=false;
}
}
}
//更新所有店家的优先级
static void aupdate(int b,int[] q){
for(int i=1;i<=n;i++){
boolean isin=false;
//当前时间,当前店家的订单数
int num=1;
for(int j=0;j<q.length;j++){
if(i==q[j]){
isin=true;
//计算出当前时间,当前店家是否有多个订单
int k=j+1;
for(;k<q.length;k++){
if(q[k]!=q[j]){
break;
}
}
num=k-j;
break;
}
}
if(isin){
a[i]+=2*num;
continue;
}
//当前时间没有订单数的,统一减去时间间隔
a[i]-=b;
//最低减少到0
if(a[i]<0){
a[i]=0;
}
}
}
}
4.实现代码2(优化后的模拟)(100%分数)
public class _G外卖店的优先级1 {
static int n;
static int m;
static int t;
static Log[] logs;
static int[] a;//动态存储每个id对应的优先级情况
static int[] last;//存放上一个有订单的时刻
static boolean[] isCache;//id是否在缓存中
static class Log implements Comparable<Log>{
int ts;
int id;
public Log(int ts, int id) {
this.ts = ts;
this.id = id;
}
@Override
public int compareTo(Log o) {
if(ts==o.ts){
return id-o.id;
}else{
return ts-o.ts;
}
}
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
t=sc.nextInt();
logs=new Log[m];
a=new int[n+1];
last=new int[n+1];
isCache=new boolean[n+1];
for(int i=0;i<m;i++){
logs[i]=new Log(sc.nextInt(),sc.nextInt());
}
solve();
}
static void solve(){
//日志按照时间进行排序
Arrays.sort(logs);
for(int i=0;i<m;){
int j=i+1;
for(;j<m;j++){
if(logs[j].ts==logs[i].ts && logs[j].id==logs[i].id){
continue;
}else{
break;
}
}
//此时[i,j-1]区间内所有的ts和id是相同的
//计算出同一个时刻,同一个店家接的单数
int cnt=j-i;
int id=logs[i].id;
int t=logs[i].ts;
a[id]-=t-last[id]-1;
if(a[id]<0){
a[id]=0;
}
if(a[id]<=3){
isCache[id]=false;
}
a[id]+=2*cnt;
if(a[id]>5){
isCache[id]=true;
}
last[id]=t;
i=j;
}
//统计每个订单最后一个订单的时候距离规定的t还有多久,需要减去相应的优先级
for(int i=1;i<=n;i++){
if(last[i]<t){
a[i]-=t-last[i];
if(a[i]<=3){
isCache[i]=false;
}
}
}
//统计答案
int ans=0;
for(int i=1;i<=n;i++){
if(isCache[i]){
ans++;
}
}
System.out.println(ans);
}
}
H.人物相关性
1.原试题
- 输入样例:
20
This is a story about Alice and Bob.Alice wants to send a private message to Bob.
- 输出样例:
2
2.简要分析
- 读题,读懂题目输出说Alice和Bob同时出现的次数是什么意思。我们可以这样理解:在Alice的长为2k的区间范围内,每出现一个Bob算一次同时出现。
- 理解题目意思后,我们首先需要统计Alice,Bob出现的位置。把他们出现的位置存在一个集合中。
- 然后计算出每个Alice满足条件的区间,因为多个Alice会有叠加的部分,叠加就需要重复对很长一段的区间同时加上一个数,这样肯定会超时,我们可以用差分数组+前缀和的方式来优化这种对一个区间同时进行的操作。
- 最后遍历Bob每个出现的位置,计算能产生的收益即可。
3.实现代码(差分数组+前缀和)
- 以下代码可以通过,并取得100%分数。
public class _H人物相关性 {
static int k;
static char[] s;
static List<Integer> Alice=new ArrayList<>();
static List<Integer> Bob=new ArrayList<>();
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
k=sc.nextInt();
sc.nextLine();
s=sc.nextLine().toCharArray();
solve();
}
static void solve(){
for(int i=0;i<s.length-4;i++){
//满足单词开头的条件
if(i==0 || s[i-1]=='.' || s[i-1]==' '){
//满足Alice
if(s[i]=='A' && s[i+1]=='l' && s[i+2]=='i' && s[i+3]=='c' && s[i+4]=='e'){
//满足单词结尾的条件
if(i+5==s.length || s[i+5]=='.' || s[i+5]==' '){
Alice.add(i);
i+=5;
continue;
}
}
}
}
for(int i=0;i<s.length-2;i++){
//满足单词开头的条件
if(i==0 || s[i-1]=='.' || s[i-1]==' '){
//满足Bob
if(s[i]=='B' && s[i+1]=='o' && s[i+2]=='b'){
//满足单词结尾的条件
if(i+3==s.length || s[i+3]=='.' || s[i+3]==' '){
Bob.add(i);
i+=3;
continue;
}
}
}
}
long ans=0;
int a[]=new int[s.length+1];//差分数组
int pre[]=new int[s.length+1];//前缀和数组
for(int i:Alice){
//i-k-3就是前面出现Bob的位置
a[Math.max(0,i-k-3)]++;
//i+k+5就是后面出现Bob的位置
a[Math.min(s.length,i+k+5)]--;
//整个的操作相当于对这个区间所有的数加1
}
pre[0]=a[0];
//计算差分数组的前缀和
for(int i=1;i<=s.length;i++){
pre[i]=pre[i-1]+a[i];
}
//统计Bob的每个位置能够确定同时出现的次数
for(int i:Bob){
ans+=pre[i];
}
System.out.println(ans);
}
}
I.后缀表达式
1.原试题
2.简要分析
- 这是一个思维类的题目,并不是什么要枚举所有可能取一种最大的值,其实就是看能不能想到。
- 第一个要明确的点是,计算的是后缀表达式,不是普通算式,所以即便是没有括号,也是可以用后缀表达式算出带有括号的值的(隐性存在的括号),这是由后缀表达式计算的性质可以看出来的。
- 既然有隐性括号的存在,那么负号是可以通过后缀表达式变成正号的。
- 所以,其实不管加号减号有多少,最后的情况只有以下几种可能:
可能情况 | 计算结果 |
---|---|
没有负号 | 所有数之和 |
没有负数(有负号) | 除了最小数外,其他所有数减去最小数 |
全部是负数(有负号) | 除了最大负数外,其他所有数减去最大负数 |
其余情况 | 所有绝对值之和 |
- 可能一时脑袋里会蹦出各种反例,但仔细去想一下,都是可以排除掉的。
3.实现代码
- 以下代码可以通过,并且取得100%分数。
public class _I后缀表达式 {
static int n;
static int m;
static int[] a;
static int min=(int)1e9+1;//所有数的最小值
static int negNum;//负数的数目
static long sum;//所有数的和
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
a=new int[n+m+1];
for(int i=0;i<a.length;i++){
a[i]=sc.nextInt();
sum+=a[i];
min=Math.min(min,a[i]);
if(a[i]<0){
negNum++;
}
}
solve();
}
static void solve(){
//没有负号
if(m==0){
System.out.println(sum);
return;
}
//没有负数
if(negNum==0){
System.out.println(sum-min-min);
return;
}
//全部是负数
if(negNum==n+m+1){
sum=0;
min=(int)1e9+1;
for(int i=0;i<a.length;i++){
sum+=Math.abs(a[i]);
min=Math.min(min,Math.abs(a[i]));
}
System.out.println(sum-min-min);
return;
}
//其余情况
long ans=0;
for(int i=0;i<a.length;i++){
ans+=Math.abs(a[i]);
}
System.out.println(ans);
}
}
J.灵能传输
1.原试题
2.简要分析
-
题目很长,花里胡哨,可能很多人最后都不会花时间去看,但仔细看完题目,会发现,题目锁描述的问题并不是很复杂(不是说不难)。
-
题意大概是这个样子的;现在有
a1,a2...an
n个数,有一个操作,使得里面的某一个数ai
,1<i<n
,进行变换a[i+1]+=a[i],a[i-1]+=a[i],a[i]-=2a[i]
,假设操作次数不限,那么max(a1,a2...,an)
的最小值是多少? -
我们仔细分析一下:
- 每一次操作是
(a[i-1],a[i],a[i+1])--->(a[i-1]+a[i],-a[i],a[i+1]+a[i])
,如果不断的使用这个变换对数组进行调整的话,会非常复杂。 - 我们观察上面这个式子,发现
a[i]
左右两边的数的变换都直接和它有关,那么我们可以用一个前缀和式子来表示整个a数组。(为什么会想到前缀和数组,因为每两项紧密关联,而且操作次数若干,很容易联想到差分数组和前缀和,而差分数组主要用来对区间的同加同减操作,所以尝试使用前缀和数组来将这个式子表达出来) - 假设
pre[i]
表示数组中前i
个数的和,也就是前缀和数组。 - 那么
a[i]=pre[i]-pre[i-1]
。 - 进行上述的变换会导致
pre
数组的变换:(pre[i-1],pre[i],pre[i+1])--->(pre[i],pre[i-1],pre[i+1])。
- 可以发现,对前缀和数组产生影响的就是,交换了
pre[i]
和pre[i-1]
。也就是交换两个相邻的数。 - 现在原问题可以转换为:给出序列
pre[i]
,其pre[1]
到pre[n-1]
可以自由交换,求s[i]-s[i-1]
中的绝对值的最大值最小可以是多少,其中1<=i<=n,pre[0]=0
。 - 假设这个序列不加其它限制,那么这个最大值中的最小值肯定是在排序后取得的。(因为保证了相邻两个的差都是最小的)
- 现在,问题是
pre[0]
和pre[n]
都是固定的,是不可以自由交换的。排序后,pre[0]
和[pre[n]
可能都到了中间去了。但是我们要求的差值还是在pre[0]
和pre[n]
都是固定的情况下求的。所以我们需要以pre[0]
为起点,pre[n]
为终点构造一个序列。 - 排序肯定还是要排的,由于中间所有数都需要取到且不重复取,所以我们需要采取一种好的策略来取这些数,比较好的方法就是隔一个数取一个数,并且用一个布尔数组来记录是否取过,这样就可以保证所有数都取到,并且,保证了相邻的差都尽可能小。
- 每一次操作是
-
依照上面的分析,就可以比较容易的写出实现的代码了。
-
视频讲解:戳我前往
3.实现代码
- 以下代码可以通过并取得100%的分数。
public class _J灵能传输 {
static int n;
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int T=sc.nextInt();
while(T-->0){
n=sc.nextInt();
long pre[]=new long[n+1];
pre[0]=0;
for(int i=1;i<=n;i++){
pre[i]=sc.nextInt();
pre[i]+=pre[i-1];
}
System.out.println(solve(pre));
}
}
static long solve(long[] pre){
//记录pre[0]和pre[n]的值,方便找到新下标
long pre0=pre[0];
long pren=pre[n];
Arrays.sort(pre);
//保证pre0比pren小
if(pre0>pren){
long temp=pre0;pre0=pren;pren=temp;
}
//找pre[0]的新下标
pre0=Arrays.binarySearch(pre,pre0);
//找pre[n]的下标
pren=Arrays.binarySearch(pre,pren);
boolean[] st=new boolean[n+1];//记录当前点是否已加入a数组
long[] a=new long[n+1];
int left=0;
int right=n;
//从pre0向左边取数
for(int i=(int)pre0;i>=0;i-=2){
a[left++]=pre[i];
st[i]=true;
}
//从pren向右边取数
for(int i=(int)pren;i<=n;i+=2){
a[right--]=pre[i];
st[i]=true;
}
//取剩下的
for(int i=0;i<=n;i++){
if(!st[i]){
a[left++]=pre[i];
}
}
long ans=0;
for(int i=1;i<=n;i++){
ans=Math.max(ans,Math.abs(a[i]-a[i-1]));
}
return ans;
}
}
总结
-
虽然这次的题整体来说比2018年的要容易些,并且题目梯度很好,题也很好,一步步的想,容易想到一些好的做法。
-
填空题4个都是可以暴力解决的,一个编程一也是这样的。只要会枚举和模拟,基本上就能拿到70分左右。
-
列举一下考点:
- 1.枚举+模拟。
- 2.DFS迷宫问题。
- 3.模拟优化。
- 4.差分数组。
- 5.数学思维。
-
1-4题和第6题不用说了,都是枚举就行了,不过要注意题目的各种条件,要细心。
-
第5题是简单的DFS迷宫问题,用Excel也可以做,但是容易出错。
-
第7题外卖店的优先级,需要能够想到记录上一次有订单的时间来优化大量不必要的整个数组操作。
-
第8题人物相关性,暴力也可以得一部分,需要能够想到差分数组优化区间的同加同减操作。
-
第9题纯粹的数学思维,要对后缀表达式比较了解,盲做的话基本上是做不出的。不过也可以枚举一些想到的特殊情况,从而拿一部分分数。
-
第10题,需要一步步的简化问题,需要平时做题中积累经验,能够想到前缀和数组的转换一级固定首尾后排序数的取法。
-
如果能把每个题基本的分数(不需要很强的算法思维的那种,或者算法题的暴力解法,特殊解法),然后再花时间做出了一两道感觉有挑战性的题的话,基本上就是100分左右了。
-
这次2019的题体验很好,比往届的题感觉要好些,估计蓝桥杯改革了,哈哈哈哈哈。
ATFWUS Writing 2021-2-2