为什么说最简单,因为本人就是一个算法小白,只学过一点数据结构,打算备战蓝桥杯的,网上说备战蓝桥杯就去刷洛谷,早有听闻洛谷很难,今天一看算是真的被打醒了,对于小白是真的太难了。(;´༎ຶД༎ຶ`)
解题之前,先了解一下Java快速输入输出工具。
Java(最)快速输入输出工具:
首先,听说Java输入输出有快速的方法,于是乎做这道题,在网上搜了一些快速输入输出的方法,我觉得这个东西就是理解为模板吧,就类似于Scanner,一开始学的时候也没问它到底什么原理(说实话现在也还不知道),清楚怎么用就行。同样道理,(最)快速输入输出也是,知道他怎么用就行。下面介绍Java的(最)快速输入:StreamTokenizer 以及快速输出:PrintWriter 。
实例化以及调用模板:
public class Main {
// (最)快速输入输出模板
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));// 输入,相当于scanner.xxx
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));// 输出,相当于System.out.xxx
// (最)快速输入输出模板
public static void main(String[] args) throws IOException {
// ....代码
}
}
两个东西有点臭长的感觉,但是仔细一看,其实他们实例化的构造函数参数还是有相同之处的,都用了缓冲流,如果想要了解更多,这里也转载一篇大佬的文章,讲解的很详细,它里面还讲了一种不同于普通的Scanner的快速输入法。
下面再列举一个测试样例,帮助理解使用这两个工具。用之前,有一些注意事项:
1. 只能输入数字和普通字符(例如输入:"~!@#$%^&*()_+{}:<>?"会用null代替)
2. 每次调用in.val或者in.sval之前,必须先调用in.nextToken();
3.用PrintWriter打印内容(即调用out.print()/out.println()之后)必须在后面调用out.close(),否则不会打印任何内容
测试样例代码:
package Test;
import java.io.*;
public class Main {
// (最)快速输入输出模板
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));// 输入,相当于scanner.xxx
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));// 输出,相当于System.out.xxx
public static void main(String[] args) throws IOException {
// 输入数字类型:
in.nextToken();
int a = (int) in.nval; // 由于in.nval返回的是double类型,所以在输入数字的时候还要强制类型转换一下
in.nextToken();
int b = (int) in.nval;
out.print(a + b);
// 输入字符串类型:
String str = "";
while (in.nextToken() != StreamTokenizer.TT_EOL) {
str = in.sval; // 这个循环可以不断的将字符串类型测试数据输入到str中(每一次循环以换行分割)
/** 但是这样在自己eclipse上可能不太好测试,因为不能停止循环,只能控制次数的用下面这样:
in.nextToken();
str += in.sval;
in.nextToken();
str += in.sval;
...
*/
}
out.close();
}
}
其实StreamTokenizer 很简单,就只能输入数字和字符串,数字的输入默认是double类型,所以转换其他数字类型直接强制类型转换一下就行。但如果想字符串类型变成字符型或者其他,那就涉及比较多方法了,常用的有像:String类的toCharArray();还有字符串分割split();,我的一篇文章中总结过String转其他类型的方法。
还有,为什么敢说最快呢,下面是我用普通的Scanner和System.out.print法输入输出 做洛谷最简单的一道P1001 A+B Problem,和用StreamTokenizer+PrintWriter做的运行时长对比:
普通方法:
快速方法:
题解:
为什么会想到快速输入输出工具呢?其实没什么特别的帮助,只是想让自己代码更快点,据说比赛的时候最好用快读快输。
所以抛开快读和块输的话,我做这题就是简单的用两次相同的循环,分开打印两种不同分制的结果,关键利用了for循环的特性,在第一层for循环中 i 是控制每场的开始位置, j 就控制每一局内部的移动,有点类似双指针法的思想。
代码1.0:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class LQBDay3 {
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
public static void main(String[] args) throws IOException {
int w11 = 0; // 11分制华华获胜次数
int l11 = 0; // 11分制华华对手获胜次数
int w21 = 0; // 21分制华华获胜次数
int l21 = 0; // 21分制华华对手获胜次数
char[] ch = null;
String str = "";
in.nextToken();
str += in.sval; // 先加入第一行对局情况
while(true) { // 再循环加入对局情况
if(str.indexOf('E') == -1) {// 如果这一行不存在E,则继续输入
in.nextToken();
str += in.sval;
}else { // 否则,退出输入
break;
}
}
// 用一个字符数组存储全部输入的对局情况,上面的str是不包括'\n'(回车)的
ch = str.toCharArray();
for(int i = 0; i < ch.length; i+= 11) { // i以11个为一组
int j = i;
w11 = 0;
l11 = 0;
// 下面循环是判断一局对战,华华和对手胜负情况的
while(ch[j] != 'E') { // 显然,退出循环的条件是遇到E了
if(ch[j] == 'W') {
w11++;
}else if(ch[j] == 'L') {
l11++;
}
// 判断一场比赛结束的条件是否满足
if(w11 + l11 >= 11) { // 由于上面for循环中,i我默认控制的是自增11,但是当两人比赛超过了11场,需要让这场比赛的区间扩大,下面有可能需要让i++
if(Math.abs(w11 - l11) >= 2 && (w11 >= 11 || l11 >= 11)) { // 差值大于2则说明这局比赛已分胜负,并且
break;
}else { // 此时和已经大于11了,但是还是没分出胜负,例如:5:6,显然这时候还没分出胜负,
// 这时为了让下一场比赛的i在正确的位置,需要让i++,理解为i是控制一场比赛的次数的区间长度
i++;
}
}
// j移动表示一局比赛的下一个回合
j++;
}
out.println(w11 + ":" + l11);
}
out.println();
for(int i = 0; i < ch.length; i+= 21) { // i以11个为一组
int j = i;
w21 = 0;
l21 = 0;
while(ch[j] != 'E') {
if(ch[j] == 'W') {
w21++;
}else if(ch[j] == 'L') {
l21++;
}
// 判断退出一场比赛的条件是否满足
if(w21 + l21 >= 21) {
if(Math.abs(w21 - l21) >= 2&& (w21 >= 21 || l21 >= 21)) { // 差值大于2则说明这局比赛已分胜负
break;
}else { // 此时和已经大于11了,但是还是没分出胜负,例如:5:6,显然这时候还没分出胜负,
// 这时为了让下一场比赛的i在正确的位置,这时只需要让i++
i++;
}
}
j++;
}
out.println(w21 + ":" + l21);
}
out.close();
}
}
显然,两个比赛计算得分情况的代码重复了,一位优秀的程序猿应该要懂得把类似的方法抽象出来,下面是优化之后的代码:
代码2.0:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
public static void main(String[] args) throws IOException {
char[] ch = null;
String str = "";
in.nextToken();
str += in.sval; // 先加入第一行对局情况
while(true) { // 再循环加入对局情况
if(str.indexOf('E') == -1) {// 如果这一行不存在E,则继续输入
in.nextToken();
str += in.sval;
}else { // 否则,退出输入
break;
}
}
// 用一个字符数组存储全部输入的对局情况,上面的str是不包括'\n'(回车)的
ch = str.toCharArray();
MatchSituation(ch, 11); // 打印11分制情况
out.println(); // 空一行
MatchSituation(ch, 21); // 打印21分制情况
out.close();
}
/**
*
* @param ch(比赛情况)
* @param system(11分制/21分制)
*/
public static void MatchSituation(char[] ch, int system) {
for(int i = 0; i < ch.length; i+= system) { // i以system(11/21分制)个为一组
int j = i;
int win = 0; // 华华获胜的场数
int lose = 0; // 华华失败的场数
// 下面循环是判断一局对战,华华和对手胜负情况的
while(ch[j] != 'E') { // 显然,退出循环的条件是遇到E了
if(ch[j] == 'W') {
win++;
}else if(ch[j] == 'L') {
lose++;
}
// 判断一场比赛结束的条件是否满足
if(win + lose >= system) { // 由于上面for循环中,i我默认控制的是自增11/21,但是当两人比赛超过了11/21场,需要让这场比赛的区间扩大,下面有可能需要让i++
if(Math.abs(win - lose) >= 2 && (win >= system || lose >= system)) { // 差值大于2则说明这局比赛已分胜负,并且
break;
}else { // 此时和已经大于11/21了,但是还是没分出胜负,例如11分制中:5:6,显然这时候还没分出胜负,
// 这时为了让下一场比赛的i在正确的位置,需要让i++,理解为i是控制一场比赛的次数的区间长度
i++;
}
}
// j移动表示一局比赛的下一个回合
j++;
}
out.println(win + ":" + lose);
}
}
}
运行结果图:
我认为上面的题解应该比较好懂了,小白刷题第一天,加油加油!