Java在算法竞赛中的一些技巧
阅读原文时间:2023年07月10日阅读:1

转载请注明出处(~ ̄▽ ̄)~

  谈到算法竞赛中使用Java,那么有一个绕不开的点就是如何快速地输入输出。通常来说,Scanner类固然可以帮助我们顺利地完成各种输入要求,而syso(System.out.print)也能够满足一般的输出要求,但是在内存以及时间的消耗上却不尽人意。同时,我发现国内博客似乎对Java在算法竞赛方面的技巧讨论得比较少,即使有也相对分散,不够深入。

  那么今天我就想分享一下自己在网上所看到的一些Java在算法竞赛方面的小技巧,希望对以Java作为算法竞赛主要语言的人有所帮助。

Java的快速输入

  算法竞赛的输入模式中,最难处理的莫过于“输入包括多行,读到文件末尾为止”这种输入要求,那今天就以这种输入要求为例。

  1. 利用StreamTokenizer

    //从标准输入流输入
    StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

比如经典的A+B问题,就可以这么去写

StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
while(st.nextToken()!=StreamTokenizer.TT_EOF) {
    int a,b;
    a=(int)st.nval;
    st.nextToken();
    b=(int)st.nval;
    pw.println(a+b);
}

如果处理的是字符串,则应该这么写

StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
st.nextToken();
String str = st.sval;

即每次先使用st.nextToken(),再调用nval或sval。

优点

代码量少,便于记忆。

缺点

不能将12345这样的纯数字字符串通过sval读入,StreamTokenizer会将其识别为数字Token,只能通过nval读入,再利用String.valueOf()等方法转化为字符串。

2. 利用InputReader(非内建类)

//本人有少许改动
static class InputReader{
    BufferedReader br;
    StringTokenizer st;

    public InputReader(){
        br = new BufferedReader(new InputStreamReader(System.in));
    }

    String next() {
        while(st==null||!st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            }catch(Exception e) {
                break;
            }
        }
        return st.nextToken();
    }

    int nextInt() {
        return Integer.parseInt(next());
    }

    String nextLine(){
        String str = "";
        try {
            str = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return str;
    }

    BigInteger nextBigInteger() {
        BigInteger bi = null;
        try {
            bi = new BigInteger(br.readLine());
        }catch(Exception e) {

        }
        return bi;
    }

}

  InputReader应该是Java快读方面用得比较多的一个类了,它是由Petr大神设计的一个类,具有适用面广泛,相对好记的特点。(虽然其中使用到的StringTokenizer在官方文档中被评价道:"StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code." 但是借用同学的话,这是宝贵的遗产,233)

  使用的话非常简单,他的API名称跟Scanner保持了一致,也能直接“顾名思义”。

那么如何处理多行输入呢,我是这么写的:

InputReader ir = new InputReader();
int a,b;
try {
    while((a = ir.nextInt())!= Float.NaN && ( b = ir.nextInt())!= Float.NaN ) {
        System.out.println(a+b);
    }
}catch(Exception e) {

}

优点

通用性强,易用性高

缺点

速度上也许是本文三种快速读中相对最慢的(相差不大),相比第一种方法相对要记多一些代码

3. 自定义读取和处理字节的函数

private byte[] inbuf = new byte[1024];
public int lenbuf = 0, ptrbuf = 0;

private int readByte()
{
    if(lenbuf == -1)throw new InputMismatchException();
    if(ptrbuf >= lenbuf){
        ptrbuf = 0;
        try { lenbuf = System.in.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); }
        if(lenbuf <= 0)return -1;
    }
    return inbuf[ptrbuf++];
}

private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); }
private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; }

private double nd() { return Double.parseDouble(ns()); }
private char nc() { return (char)skip(); }

private String ns()
{
    int b = skip();
    StringBuilder sb = new StringBuilder();
    while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
        sb.appendCodePoint(b);
        b = readByte();
    }
    return sb.toString();
}

private char[] ns(int n)
{
    char[] buf = new char[n];
    int b = skip(), p = 0;
    while(p < n && !(isSpaceChar(b))){
        buf[p++] = (char)b;
        b = readByte();
    }
    return n == p ? buf : Arrays.copyOf(buf, p);
}

private char[][] nm(int n, int m)
{
    char[][] map = new char[n][];
    for(int i = 0;i < n;i++)map[i] = ns(m);
    return map;
}

private int[] na(int n)
{
    int[] a = new int[n];
    for(int i = 0;i < n;i++)a[i] = ni();
    return a;
}

private int ni()
{
    int num = 0, b;
    boolean minus = false;
    while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
    if(b == '-'){
        minus = true;
        b = readByte();
    }

    while(true){
        if(b >= '0' && b <= '9'){
            num = num * 10 + (b - '0');
        }else{
            return minus ? -num : num;
        }
        b = readByte();
    }
}

private long nl()
{
    long num = 0;
    int b;
    boolean minus = false;
    while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
    if(b == '-'){
        minus = true;
        b = readByte();
    }

    while(true){
        if(b >= '0' && b <= '9'){
            num = num * 10 + (b - '0');
        }else{
            return minus ? -num : num;
        }
        b = readByte();
    }
}

  上述相关函数由uwi大神设计,orz,自己亲自处理字节,速度当然是杠杠的。函数名符合一贯竞赛er的抽象惯例 在处理多行A+B问题时,我的写法和方法二类似,即:

int a,b;
try {
    while((a = ni())!= Float.NaN && ( b = ni())!= Float.NaN ) {
        System.out.println(a+b);
    }
}catch(Exception e) {

}

优点

速度快 够装逼

缺点

emmm,应该不太可能现场背下来,比较适合网络赛和平时练习之类的

Java的快速输出

  相比于快速输入的各种神仙写法,快速输出可以说是非常友好了,不仅便于记忆,而且能在很短的时间内写出来。

PrintWriter pw = new PrintWriter(System.out);
pw.println(//和System.out.println用法一样);
pw.close();//这句话在最后必须有,不然不会将结果输出

  由于PrintWriter自带Buffer,所以不需要再对System.out再套(娃)其他的缓冲输出流。简直不要太好用有木有?

  快速输入输出这套组合拳,如果你拿来跟之前的Scanner和System.out输入输出相比,你就会发现时间和空间耗费大大减少了,以前A不过的题再也不是问题了。并不

Java加栈

  还有一个问题,爆栈了怎么办?特别是遇上数据量大的题目,爆栈简直不要太容易,当然我们可以通过改递归为遍历等方法来规避这个问题,但是有没有什么奇技淫巧呢?当然有:

//还是以A+B问题为例
public class Main implements Runnable {
     public static void main(String[] args) {
         //whatever可替换为任何内容
         new Thread(null, new Main(), "whatever", 1<<26).start();
     }

     public void run() {
        Scanner cin = new Scanner(System.in);
        int a, b;
        while (cin.hasNext()){
            a = cin.nextInt(); b = cin.nextInt();
            System.out.println(a + b);
        }
     }
}

  没错,方法就是开一个线程,通过为线程申请空间从而解决这个问题。但是注意,一是start方法执行后也许不会立即执行run方法,二是申请的空间大小是平台相关的,不保证一定有效。

  后记:虽然本文介绍了快速读写和加栈的方法,但是归根结底,算法竞赛重点还是希望大家写出高效的算法,这些只是一些辅助的内容,时间复杂度不对,单凭这些“优化”仍是无法解决问题的。(此外,遇到腹黑的出题人,即便你的时间复杂度没问题,也用了快速输入输出,仍然是无法AC的,这就要谈到Java在算法竞赛圈中的卑微地位了(划掉),所以最好的优化就是使用C++(狗头)。而关于如何转向C++,可以参考我的第一篇博客。)

  要是觉得不错,就点个“推荐”吧 φ(゜▽゜*)♪

如有错误,还望不吝指出。

转载请注明出处(~ ̄▽ ̄)~