刷洛谷题单第二天,今天第一道题(P2670 [NOIP2015 普及组] 扫雷游戏)还是挺简单的,但是又被现实给了一级大逼兜子,做这题关键还是想熟悉Java的快速输入。还有做这道题很容易出现RE错误,下面总结了常见的RE错误导致情况(点击跳转下文)。还用BigInteger爆杀了P1009 [NOIP1998 普及组] 阶乘之和问题(点击跳转下文),当然,作为刷算法,还用数组模拟了P1601 A+B Problem(高精)问题(点击跳转下文)
P2670 [NOIP2015 普及组] 扫雷游戏
P2670 [NOIP2015 普及组] 扫雷游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
我的上篇文章详细讲关于Java最快速输入输出法(关于StreamTokenizer和PrintWriter的应用)但是,StreamTokenizer有个不足就是,不能输入特殊符号,而今天做的洛谷这道题P2670 [NOIP2015 普及组] 扫雷游戏需要输入“*”和“?”,所以肯定得另外找过一个块输法了,这里就有另一个块输工具:BufferedReader。
BufferedReader实例化以及使用模板:
public class Main {
// 用BufferedReader才能读特殊字符
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException { // 注意这里一定要抛出异常
// 输入数字:
String[] dimensions = in.readLine().split(" "); // 例如,输入2 3
int n = Integer.parseInt(dimensions[0]); // 2
int m = Integer.parseInt(dimensions[1]); // 3
// ...代码
// 输入字符串:
String str = in.readLine();
// 循环输入字符串:
String line;
while ((line = in.readLine()) != null) {
// 当你输入一个空行并按下回车时,程序将停止读取
if (line.isEmpty()) {
break;
}
System.out.println(line);
}
// ...代码
}
}
什么就是BufferedReader常用的输入数字和字符串的方法,大家会发现,输入数字的时候要利用String类的split方法分割然后用Integer类转换才能得到,看上去有点麻烦,而BufferedReader中恰好有一个直接返回int型的方法:read();这不可以直接输入吗?哈哈,本人就是踩了这个坑,没了解方法内部具体实现情况,只看返回值来使用,导致一开始数据怎么都输入不进去。
BufferedReader中的read();方法确实是返回int型,但是,它返回的是输入字符的ASCII码值。所以,想要输入数字,只能用上面实例中的代码,防止踩坑啊。
扫雷游戏题解:
思路:
我的思路是:定义一个int型的二维数组作为雷区结果集minefield (最后根据这个集合来输出),遍历ch字符二维数组,当遇到一个雷,首先给雷区数组minefield 的这个位置设置一个最小值(Integer自带的MIN_VALUE,是个很小的负数),然后扫描它的八个方向,满足没有造成数组越界情况时,给雷区数组周围的位子都自增1,这样结束之后,在结果集minefield 中,非雷区就会标记周围雷区情况,代码如下:
代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class Main {
// 用BufferedReader才能读特殊字符
static BufferedReader in = 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 {
String[] dimensions = in.readLine().split(" ");
int n = Integer.parseInt(dimensions[0]); // 行数
int m = Integer.parseInt(dimensions[1]); // 列数
char[][] ch = new char[n][m];
int[][] minefield = new int[n][m];
for(int i = 0; i < n; i++) {
String str = in.readLine();
ch[i] = str.toCharArray();
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(ch[i][j] == '*') {
minefield [i][j] = Integer.MIN_VALUE;
// 注意是否越界条件,否则在洛谷提交中会出现RE错误
if((i - 1) >= 0) {
minefield [i - 1][j]++;
}
if((i - 1) >= 0 && (j + 1) <= m - 1) {
minefield [i - 1][j + 1]++;
}
if((j + 1) <= m - 1) {
minefield [i][j +1]++;
}
if((i + 1) <= n - 1 && (j + 1) <= m - 1) {
minefield [i + 1][j + 1]++;
}
if((i + 1) <= n - 1) {
minefield [i + 1][j]++;
}
if((i + 1) <= n - 1 && (j - 1) >= 0) {
minefield [i + 1][j - 1]++;
}
if((j - 1) >= 0) {
minefield [i][j - 1]++;
}
if((i - 1) >= 0 && (j - 1) >= 0) {
minefield [i - 1][j - 1]++;
}
}
}
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(minefield [i][j] < 0) {
out.print('*');
}else {
out.print(minefield [i][j]);
}
}
out.println();
}
out.close();
}
}
运行结果图:
这题难就难在八个方向的判断这里,很容易出现数组越界错误,当出现数组越界错误时,洛谷会出现一个叫“RE”的错误,RE:运行时错误。
RE错误常见的情况:
- 数组越界:尝试访问数组超出其定义范围的元素。
- 空指针解引用:试图通过空指针访问对象或内存。
- 除以零:在整数或浮点数除法中,除数为零。
- 栈溢出:通常由于无限递归或过大的局部变量/数组分配导致。
- 内存溢出:尝试分配超过可用内存的空间。
- 访问非法地址:试图读取或写入不属于程序的内存地址。
- 使用未初始化的变量:在某些情况下,使用未初始化的变量可能导致运行时错误。
- 浮点数异常:如除以零、溢出、下溢或非法的浮点操作。
洛谷高精度阶乘之和:
自从知道了Java自带处理高进度的类(BigInteger)妈妈再也不用担心我不会做高精度题啦(下文纯属熟练Java的BigInteger类,并非用数组求解的一般解法题)
首先总结一下BigInteger常用方法:
BigInteger常用方法:
构造方法:
BigInteger(String val)
:将字符串转换为 BigInteger。
算术运算:
BigInteger add(BigInteger val)
:返回其值为(this + val)
的 BigInteger。BigInteger subtract(BigInteger val)
:返回其值为(this - val)
的 BigInteger。BigInteger multiply(BigInteger val)
:返回其值为(this * val)
的 BigInteger。BigInteger divide(BigInteger val)
:返回其值为(this / val)
的 BigInteger。整数除法,丢弃小数部分。
静态方法:
BigInteger valueOf(long val)
:返回其值等于指定long
的值的 BigInteger。BigInteger ONE
:BigInteger 常量 1。BigInteger ZERO
:BigInteger 常量 0。
那么这道题,利用BigInteger类,那就不用担心溢出的问题了,直接正常的求阶乘即可,求阶层很简单,用一个temp储存从1~n 之间每一个数的阶层,然后用一个sum累加即可,其实一个for循环就行,因为每次temp都保存了上一个数的阶乘,即:n! = (n - 1)! * n;
因此会用到 BigInteger.ZERO 来初始化sum,用 BigInteger.ONE 初始化temp为1,当要累乘时,要用到multiply,以及把for循环里的i转化为BigInteger,要用上BigInteger.valueOf(i),综上便可以写出以下代码:
代码如下:
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.math.BigInteger;
public class Main {
static BufferedReader in = 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 {
String n = in.readLine();
int N = Integer.parseInt(n);
BigInteger sum = BigInteger.ZERO;
BigInteger temp = BigInteger.ONE;
for(int i = 1; i <= N; i++) {
temp = temp.multiply(BigInteger.valueOf(i));
sum = sum.add(temp);
}
out.print(sum);
out.close();
}
}
也是舒舒服服的AC了(*^▽^*)
当然~这是一道算法题,对于高精度的运算,本质上还是一种模拟,用数组模拟,所以接下来,用模拟法做一道 P1601 A+B Problem(高精)吧(你问我为什么不做这道题了,哈哈哈,因为难的我暂时不会了)
P1601 A+B Problem(高精)
P1601 A+B Problem(高精) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
做这道题的时候呢,一开始卡住了,知道大致的思路,就是把每个数字用数组存储然后按位求和,把每一位求和的个位保留存入结果中,十位(进位位)保留到下一次按位求和中。但是在做的时候发现了下面这些问题。
遇到的问题
- 不清楚用什么类型数组存储A和B的每一位。其实根据思路,我们后续是需要拿出每一位来求和的,即:需要求“数字和”,那么用char数组就不行了,因为char不方便把字符变成数字,所以用String(用Integer.parseInt()把字符串转化为数字)
- 循环怎么写?最终结果多长?做这题的时候,思路很简单,可是自己写的时候,发现哎~这个循环咋整啊,循环啥时候停啊,还有长度是多少嘞??其实,两数之和的长度一定是不超过两数中,位数较大者+1的,所以,可以先确定结果的长度为两数中位数较大者+1,这样,循环的终止条件也可就是结果的长度,因为最后一个进位也要加上去。
代码:
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.math.BigInteger;
public class Main {
static BufferedReader in = 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 {
// 输入的结果都用字符串数组来存储,方别后续用整型存储按位求和结果
String[] chA = in.readLine().split(""); // readLine()就是按行读入数据,然后分割字符变成字符串数组
String[] chB = in.readLine().split("");
int length = (chA.length > chB.length ? chA.length : chB.length) + 1; // 这是保存最终结果长度的,因为是求和,最大长度肯定是输入的两数中最长的长度加1
StringBuilder result = new StringBuilder(); // 结果用StringBuilder,因为后面要用到字符串拼接和转置结果
int sum = 0; // 每一位求和的结果
for(int i = 0; i < length; i++) {
if(i < chA.length) { // 为了防止数组越界,有可能这个数字已经加完了,当i大于他的长度时,就不用求它的和了
sum += Integer.parseInt(chA[chA.length - i - 1]);
}
if(i < chB.length) {
sum += Integer.parseInt(chB[chB.length - i - 1]);
}
// sum为A和B的每一位之和,从个位开始
result.append(sum % 10); // 把每一位之和的个位存入结果集中,结果为倒序
sum /= 10; // 进位将会保留在下一次按位求和中
}
BigInteger a = new BigInteger("0");
a.add(val);
if(result.charAt(result.length() - 1) == '0') {
result.deleteCharAt(result.length() - 1);
}// 由于刚刚的length始终都比最长的大1,有可能出现两个数之和没有进位的情况,如 1 + 1 = 2,如果不加这段代码,结果会变成02
out.print(result.reverse());// 逆序输出
out.close();
}
}
结果图:
似乎和用BigInteger做差不多的运行时间哦: