【发布时间】:2020-11-29 01:02:11
【问题描述】:
我正在尝试解决这个 CSES 问题:Grid Paths。你得到一个长度为 48 的字符串,你必须找到路径的数量,这样你才能遍历所有网格并最终到达左下角。
我相信我已经尽我所能修剪了搜索,根据这本书:CP Handbook(在修剪搜索类别中查看),针对此类问题的最佳优化是防止您的路径关闭自己,我已经实现了这一点。这个特定问题的时间限制很紧,虽然我已经基本解决了这个问题,但我仍然失败了 1-2 个测试用例,因为我的解决方案大约需要 1.01 秒,而不是低于 1 秒的时间限制。
最后,我只是想知道是否有任何很酷的微优化可以用来稍微提高我的 java 代码的速度,这样我实际上可以通过这个问题的所有测试用例。
import java.io.*;
public class GridPaths {
public static class FastIO {
InputStream dis;
byte[] buffer = new byte[1 << 17];
int pointer = 0;
public FastIO(String fileName) throws Exception {
dis = new FileInputStream(fileName);
}
public FastIO(InputStream is) {
dis = is;
}
int nextInt() throws Exception {
int ret = 0;
byte b;
do {
b = nextByte();
} while (b <= ' ');
boolean negative = false;
if (b == '-') {
negative = true;
b = nextByte();
}
while (b >= '0' && b <= '9') {
ret = 10 * ret + b - '0';
b = nextByte();
}
return (negative) ? -ret : ret;
}
long nextLong() throws Exception {
long ret = 0;
byte b;
do {
b = nextByte();
} while (b <= ' ');
boolean negative = false;
if (b == '-') {
negative = true;
b = nextByte();
}
while (b >= '0' && b <= '9') {
ret = 10 * ret + b - '0';
b = nextByte();
}
return (negative) ? -ret : ret;
}
Integer[] readArray(int n) throws Exception {
Integer[] a = new Integer[n];
for (int i = 0; i < n; i++) a[i] = nextInt();
return a;
}
byte nextByte() throws Exception {
if (pointer == buffer.length) {
dis.read(buffer, 0, buffer.length);
pointer = 0;
}
return buffer[pointer++];
}
String next() throws Exception {
StringBuilder ret = new StringBuilder();
byte b;
do {
b = nextByte();
} while (b <= ' ');
while (b > ' ') {
ret.appendCodePoint(b);
b = nextByte();
}
return ret.toString();
}
}
static char[] board;
static boolean[][] visited = new boolean[7][7];
static int ans = 0;
public static boolean works(int i, int j) {
//makes sure that current spot is on the 7x7 grid and is not visited
return (i >= 0 && i<=6 && j>=0 && j<=6 && !visited[i][j]);
}
public static void solve(int i, int j, int steps) {
if (i == 6 && j == 0) {
if (steps == 48) ans++; //all spots of the grid have to be visited in order to be counted as part of the answer
return;
}
visited[i][j] = true;
//you are given ? characters in the input string, and those mean that you have to try out all 4 combinations (U,D,L,R)
if (board[steps] == '?' || board[steps] == 'L') {
//second condition of the second if statement checks if the spot directly ahead of the current spot is blocked, and if it is, the left and right spots cannot both be unvisited or else you will not continue searching
if (works(i,j-1) && !(!works(i,j-2) && works(i+1,j-1) && works(i-1,j-1))) {
solve(i, j - 1, steps + 1);
}
}
if (board[steps] == '?' || board[steps] == 'R') {
if (works(i,j+1) && !(!works(i,j+2) && works(i+1,j+1) && works(i-1,j+1))) {
solve(i, j + 1, steps + 1);
}
}
if (board[steps] == '?' || board[steps] == 'U') {
if (works(i-1,j) && !(!works(i-2,j) && works(i-1,j+1) && works(i-1,j-1))) {
solve(i - 1, j, steps + 1);
}
}
if (board[steps] == '?' || board[steps] == 'D') {
if (works(i+1,j) && !(!works(i+2,j) && works(i+1,j+1) && works(i+1,j-1))) {
solve(i + 1, j, steps + 1);
}
}
visited[i][j] = false;
}
public static void main(String[] args) throws Exception {
FastIO in = new FastIO(System.in);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
board = in.next().toCharArray();
solve(0,0,0);
out.println(ans);
out.close();
}
}
注意:我已经在使用一种最快(如果不是最快)的方式来接收 Java 输入,因此我认为我实际上无法改进。
【问题讨论】:
-
String
next()可能会受益于直接查看 IO 缓冲区并调用 Java 内置函数来扫描下一个字符 >=' ',如果有的话。我希望 JIT 会编译成一个字节一次的循环,但如果有长时间的空白/控制字符运行,SIMD 的速度可以提高 16 倍。同样用于查找非空白字节的范围并将其复制到字符串中;这可以避免 StringBuilder,并且(在输入缓冲区结束之前找到结尾的乐观情况下)避免在 IO 调用期间保存部分构造的 String / StringBuilder -
您是否进行了剖析以了解它在哪里花费了大部分 CPU 时间?大概在递归
solve()中,所以微优化IO大概可以忽略不计。 -
你读取的数据是什么格式的?它只是文件中48个字符的字符串吗?如果是这样,为什么 60% 的代码只是用于从只能包含 ASCII 字符组合的文件中读取字符串?我将您的 FastIO 类与 FileInputStream 类进行了比较,重复 1000 次从文件中读取随机生成的 48 个字符的字符串,并且 FileInputStream 比您的类快 3 倍。所以如果这是计时的一部分,也许你可以用 FileInputStream 替换你的 FastIO 类,你就完成了。
-
@Steve IO 只是模板的一部分。根据多个消息来源,这是目前为 Java 获取可用 IO 的最快方法之一。但是由于某种原因,我只是将其切换到 BufferedReader 并且能够在时间限制内提交所有内容。这在诸如 spoj.com/problems/INTEST 之类的问题上没有多大意义,它们有大量的输入(大约 10^7 个不同的整数),而这个 FastIO 能够在大约 0.3 秒内完成它,而只需使用BufferedReader 大约是 0.65 秒。
-
@Krish:我并不惊讶那些 I/O 函数对 large 输入流很好,因为它不能全部放在一个缓冲区中(尤其是对于整数但对于字符串单词来说可能不是那么多)。但是您有不同的情况,输入很小并且适合单个输入缓冲区,对吗?并且可能适合任何内部缓冲区标准 Java IO 类使用。如果您的大部分 CPU 时间不是 I/O,IDK 为什么这会产生很大的不同,但很明显,对于不同的用例,一段代码可能快或慢。您查看的快速测试用例是否主要读取数字?