CCF CSP认证JAVA(五)

201812-1小明上学

题目背景

小明是汉东省政法大学附属中学的一名学生,他每天都要骑自行车往返于家和学校。为了能尽可能充足地睡眠,他希望能够预计自己上学所需要的时间。他上学需要经过数段道路,相邻两段道路之间设有至多一盏红绿灯。
  京州市的红绿灯是这样工作的:每盏红绿灯有红、黄、绿三盏灯和一个能够显示倒计时的显示牌。假设红绿灯被设定为红灯 r 秒,黄灯 y 秒,绿灯 g 秒,那么从 0 时刻起,[0,r) 秒内亮红灯,车辆不许通过;[r, r+g) 秒内亮绿灯,车辆允许通过;[r+g, r+g+y) 秒内亮黄灯,车辆不许通过,然后依次循环。倒计时的显示牌上显示的数字 l(l > 0)是指距离下一次信号灯变化的秒数。

问题描述

一次上学的路上,小明记录下了经过每段路的时间,和各个红绿灯在小明到达路口时的颜色和倒计时秒数。希望你帮忙计算此次小明上学所用的时间。

输入格式

输入的第一行包含空格分隔的三个正整数 r、y、g,表示红绿灯的设置。这三个数均不超过 106。
  输入的第二行包含一个正整数 n(n ≤ 100),表示小明总共经过的道路段数和看到的红绿灯数目。
  接下来的 n 行,每行包含空格分隔的两个整数 k、t。k=0 表示经过了一段道路,耗时 t 秒,此处 t 不超过 106;k=1、2、3 时,分别表示看到了一个红灯、黄灯、绿灯,且倒计时显示牌上显示的数字是 t,此处 t 分别不会超过 r、y、g。

输出格式

输出一个数字,表示此次小明上学所用的时间。

样例输入

30 3 30
8
0 10
1 5
0 11
2 2
0 6
0 3
3 10
0 3

样例输出

70

样例说明

小明先经过第一段道路,用时 10 秒,然后等待 5 秒的红灯,再经过第二段道路,用时 11 秒,然后等待 2 秒的黄灯和 30 秒的红灯,再经过第三段、第四段道路,分别用时6、3秒,然后通过绿灯,再经过最后一段道路,用时 3 秒。共计 10 + 5 + 11 + 2 + 30 + 6 + 3 + 3=70 秒。

评测用例规模与约定

测试点 1, 2 中不存在任何信号灯。
  测试点 3, 4 中所有的信号灯在被观察时均为绿灯。
  测试点 5, 6 中所有的信号灯在被观察时均为红灯。
  测试点 7, 8 中所有的信号灯在被观察时均为黄灯。
  测试点 9, 10 中将出现各种可能的情况。

public class Main {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int r = scanner.nextInt(),y = scanner.nextInt(),g = scanner.nextInt();
        int n = scanner.nextInt();
        int total = 0;
        for(int i=0;i<n;i++){
            int k = scanner.nextInt();
            int t = scanner.nextInt();
            switch (k){
                case 0:
                case 1:
                    total += t;
                    break;
                case 2:
                    total += t+r;
                    break;
                case 3:
                    break;
            }
        }
        System.out.println(total);
    }
}

201812-2小明放学

题目背景  
汉东省政法大学附属中学所在的光明区最近实施了名为“智慧光明”的智慧城市项目。具体到交通领域,通过“智慧光明”终端,可以看到光明区所有红绿灯此时此刻的状态。小明的学校也安装了“智慧光明”终端,小明想利用这个终端给出的信息,估算自己放学回到家的时间。
问题描述  
一次放学的时候,小明已经规划好了自己回家的路线,并且能够预测经过各个路段的时间。同时,小明通过学校里安装的“智慧光明”终端,看到了出发时刻路上经过的所有红绿灯的指示状态。请帮忙计算小明此次回家所需要的时间。
输入格式  
输入的第一行包含空格分隔的三个正整数 r、y、g,表示红绿灯的设置。这三个数均不超过 106。   
输入的第二行包含一个正整数 n,表示小明总共经过的道路段数和路过的红绿灯数目。   
接下来的 n 行,每行包含空格分隔的两个整数 k、t。
k=0 表示经过了一段道路,将会耗时 t 秒,此处 t 不超过 106;k=1、2、3 时,分别表示出发时刻,此处的红绿灯状态是红灯、黄灯、绿灯,且倒计时显示牌上显示的数字是 t,此处 t 分别不会超过 r、y、g。
输出格式  
输出一个数字,表示此次小明放学回家所用的时间。

public class Main {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int r = scanner.nextInt(),y = scanner.nextInt(),g = scanner.nextInt();
        int n = scanner.nextInt();
        int m = r+y+g;
        long total = 0;//经过的时间,可能会超出int的存储范围
        for(int i=0;i<n;i++){
            int k = scanner.nextInt(),t = scanner.nextInt();
            long temp =0;
            switch (k){
                case 0:
                    total+=t;
                    break;
                case 1:
                    temp = (r-t+y+g+total)%m;
                    break;
                case 2:
                    temp = (y-t+g+total)%m;
                    break;
                case 3:
                    temp = (g-t+total)%m;
                    break;
            }
            if(temp>g){
                total+= m-temp;
            }
        }
        System.out.println(total);
    }
}

和上面一题大同小异。

201503-1图像旋转

问题描述

旋转是图像处理的基本操作,在这个问题中,你需要将一个图像逆时针旋转90度。
  计算机中的图像表示可以用一个矩阵来表示,为了旋转一个图像,只需要将对应的矩阵旋转即可。

输入格式

输入的第一行包含两个整数n, m,分别表示图像矩阵的行数和列数。
  接下来n行每行包含m个整数,表示输入的图像。

输出格式

输出m行,每行包含n个整数,表示原始矩阵逆时针旋转90度后的矩阵。

样例输入

2 3
1 5 3
3 2 4

样例输出

3 4
5 2
1 3

评测用例规模与约定

1 ≤ n, m ≤ 1,000,矩阵中的数都是不超过1000的非负整数。

public class demo15 {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(),m = scanner.nextInt();
        int[][] num1 = new int[n][m];
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                num1[i][j]=scanner.nextInt();
            }
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                System.out.print(num1[j][m-1-i]+" ");
            }
            System.out.print("\n");
        }
    }
}

思路很简单,但是最后得分90,说是内存超限!!!

也就是需要我们进一步优化。

StreamTokenizer输入优化

用JAVA解题一般用Scanner类来进行输入,但对时间要求严格的题,用它可能会超时,此时我们可以改用StreamTokenizer类进行输入,后者的效率更高。

  1. 类java.io.StreamTokenizer可以获取输入流并将其分析为Token(标记)。 StreamTokenizer的nextToken方法读取下一个标记

  2. 默认情况下,StreamTokenizer认为下列内容是Token:字母、数字、除c和c++注释符号以外的其他符号。如符号“/”不是Token,注释后的内容也不是,而"/"是Token。单引号和双引号以及其总的内容,只能算一个Token。

  3. 为了提高效率,使用BufferedReader,创建StreamTokenizer对象

    StreamTokenizer st =new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); 
    
  4. 为了从流中获取标记,可以调用StreamTokenizer的nextToken()方法。 调用nextToken()方法以后,如果标记是字符串,可用 String s=st.sval,如果是整数用 int n=(int) st.nval得到。

ACM标准输入输出JAVA版及性能优化_乔阳的博客-CSDN博客

修改后的满分代码

使用BufferedReader

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String str;
        String[] temp;
        str = bufferedReader.readLine();
        temp = str.split(" ");
        int n = Integer.parseInt(temp[0]);
        int m = Integer.parseInt(temp[1]);
        int a[][] = new int[n][m];
        for(int i=0;i<n;i++){
            str = bufferedReader.readLine();
            temp = str.split(" ");
            for (int j=0;j<m;j++){
                a[i][j]=Integer.parseInt(temp[j]);
            }
        }
        for (int i=0;i<m;i++){
            for (int j=0;j<n;j++){
                System.out.print(a[j][m-1-i]+" ");
            }
            System.out.println();
        }
    }
}

使用StreamTokenizer

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
class Input{
    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public int nextInt() throws IOException {
        in.nextToken();
        return (int)in.nval;
    }
}
public class Main {
    public static void main(String [] args) throws IOException {
        Input in = new Input();
        int n = in.nextInt(),m = in.nextInt();
        int[][] nums = new int[n][m];
        for ( int i = 0; i < n; i++ ) {
            for ( int j = 0; j < m; j++ ) {
                nums[i][j] = in.nextInt();
            }
        }
        //优化输出
        StringBuilder sb = new StringBuilder();
        for(int i=m-1;i>=0;i--){
            for(int j=0;j<n;j++){
                sb.append(nums[j][i]+" ");
            }
            sb.append("\n");
        }
        System.out.println(sb.toString());
    }
}

201412-1门禁系统

问题描述

涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况。每位读者有一个编号,每条记录用读者的编号来表示。给出读者的来访记录,请问每一条记录中的读者是第几次出现。

输入格式

输入的第一行包含一个整数n,表示涛涛的记录条数。
  第二行包含n个整数,依次表示涛涛的记录中每位读者的编号。

输出格式

输出一行,包含n个整数,由空格分隔,依次表示每条记录中的读者编号是第几次出现。

样例输入

5
1 2 1 1 3

样例输出

1 1 2 3 1

评测用例规模与约定

1≤n≤1,000,读者的编号为不超过n的正整数。

public class Main {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        Map<Integer,Integer> map = new HashMap<>();
        while (n --!=0){
            int m = scanner.nextInt();
            if(!map.containsKey(m)){
                map.put(m,1);
                System.out.print(1+" ");
            }else {
                map.put(m,map.get(m)+1);
                System.out.print(map.get(m)+" ");
            }
        }
    }
}

水题啦!很简单,五分钟完事儿!

202012-1期末预测之安全指数

在这里插入图片描述

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String [] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[] temp;
        int sum = 0;
        for(int i=0;i<n;i++){
            temp = br.readLine().split(" ");
            sum += Integer.parseInt(temp[0])*Integer.parseInt(temp[1]);
        }
        System.out.println(Math.max(0,sum));
    }
}

202012-2期末预测值最佳阈值

在这里插入图片描述

一顿操作猛如虎,一朝提交超时了

(泪如雨下.jpg)

失败总结:

暴力嵌套循环O(N^2)
是七十分,应该是由于m的上限在10^5次方吧,太大了所以双层循环会超时。

再来梳理一遍这个题目的思路:

  1. 选阈值是在y[i]里面选;
  2. 如果安全指数y大于等于本次的阈值,且不挂科;或是安全指数y小于本次的阈值,且挂科,这两种情况就是预测正确了;
  3. 我们就是要找到预测正确的次数多的对应的阈值,并且正确次数相同时候取较大的那个;

考虑如何优化:

思路

  • 首先对安全系数进行排序,按照安全系数从小到大进行排序,可以想到,在选择的阈值之前的预测为0的是正确的,在阈值之后(包括)预测为1的是正确的;
  • 排序这里参考大佬用到的是PriorityQueue,我们也可以用sort()函数来排序;
  • 排序之后,我们就可以想到,在排好序的队列中选中一个点,以它作为阈值时候预测正确的次数就等于,在它之前且挂了的 加上 在它之后(包括)没挂的;
  • 这时候我们就可以想到利用前缀和的思想,我在之前的博文中提到了这种思想,我们可以用一个数组sum来存储 i 及其之前未挂科的人数和,分别计算自己以及之后1的个数sum[num]-sum[i],以及自己之前的0的个数i-sum[i],这两部分加起来就是预测正确的次数;
  • 经分析需要进行set去重:
    1. 重复的时候,如果前面的那个是0,后面的那个是1,我们可以知道,第一部分的和二者的是相同的(自己及之后的1),但是第二部分的和,后面那个就会比前面那个多算一个0,但我们知道这个0是不该算进正确的里面的(也就是安全系数等于阈值的本应该是不挂科的,但是这个挂科的却算作正确了)
    2. 重复的时候,如果前面的那个是1,后面的那个是0,我们可以知道,第二部分的和二者是相同的(自己之前的0),但是第一部分的和,后面那个就会比前面那个少算一个1,但是这个1是应该算进正确的里面的(也就是安全系数等于阈值的是不挂科的,但是前面这个没挂科的本来是预测对了,却没有算进里面去,就少了一个)
    3. 综上所述,应该去重,保留第一次出现的那种情况,不然可能会出错!!!
    4. 最后算法复杂度为nlogn。

下面贴的是大佬的代码,原文见链接:

JAVA CCF-202012-2 期末预测之最佳阈值_不断奔跑-CSDN博客_期末预测之最佳阈值java

不得不说很巧妙了,直接假设未挂科的都是预测对的,然后加上在本轮选定的阈值之前挂科的,减去在本轮选定的阈值之前没挂科的,就是正确的次数!!!!!我直接跪了,太巧妙了!

import java.util.*;

public class Main {
    public static void main(String [] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        //预测正确最多的个数;结果;当前预测成功的个数
        int max = -1,ans = -1,cur = 0;
        // 存储所有数据,按安全指数升序
        // 使用优先级队列实现升序
        PriorityQueue<int[]> p = new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0]-o2[0];
            }
        });
        //利用set去重
        Set<Integer> set = new TreeSet<>();
        //输入
        while (n-- != 0){
            int[] temp = new int[]{in.nextInt(),in.nextInt()};
            p.add(temp);
            set.add(temp[0]);
            // 累计没挂科的人数,后面遍历遇到直接--即可。
            // 等同于 所有没挂科人数 - 当前遍历到的没挂科人数
            // 0挂科,1未挂科
            if (temp[1]==1) {
                cur++;
            }
        }
        //set按安全指数升序遍历
        for(int next: set){
            //peek(): 获取但不删除队首元素
            while (!p.isEmpty()&&p.peek()[0]<next){
                if(p.peek()[1]==1){
                    cur--;
                }else {
                    cur++;
                }
                p.poll();
            }
            if (cur>=max){
                ans=next;
                max=cur;
            }
        }
        System.out.println(ans);
    }
}

PriorityQueue

PriorityQueue类在Java1.5中引入。PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。要求使用Java Comparable和Comparator接口给对象排序,并且在排序时会按照优先级处理其中的元素。

  • 优先队列不允许空值,而且不支持non-comparable(不可比较)的对象,比如用户自定义的类。
  • 优先队列的头是基于自然排序或者Comparator排序的最小元素。如果有多个对象拥有同样的排序,那么就可能随机地取其中任意一个。当我们获取队列时,返回队列的头对象。
  • 优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。
  • PriorityQueuepeek()element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)

element()和peek()的语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小的那个元素,二者唯一的区别是当方法失败时前者抛出异常,后者返回null。根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。
remove()和poll()方法的语义也完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回null。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。


今日推歌

----《公子向北走》

愿你三冬暖
愿你春不寒
愿你天黑有灯
下雨有伞
愿你善其身
愿你遇良人
暖色浮余生
有好人相伴
所有爱慕之意
止于唇齿间
掩于岁月
匿于将来

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星回昭以烂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值