前置知识:无

核心代码模式 VS. ACM 模式

核心代码模式和 ACM 模式

高效 IO

ACM 模式的题目要自己处理数据的输入输出,使用语言不同的方法,IO 的效率相差很大

词和行

  • 词:按空白分割(包括空格、回车换行、制表符等空白字符串)
  • 行:按换行分割

如,有如下内容:

1      2
abc -28 uvw

按词读:

共读 5 次,每次读到:
1
2
abc
-28
uvw

按行读:

共读 2 次,每次读到:
1      2
abc -28 uvw

Java

  • 低效 IO:ScannerSystem.out
  • 高效 IO:BufferedReaderStreamTokenizerPrintWriter

按词读

package main;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
 
public class Main {
  public static void main(String[] args) throws IOException {
    // 带缓存的读(输入),很高效
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    // 一个词一个词的读
    StreamTokenizer in = new StreamTokenizer(br);
    // 带缓存的写(输出),很高效
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    while (in.nextToken() != StreamTokenizer.TT_EOF) { // 输入流没有结束
      int n = (int) in.nval; // 读一个词的内容,转成 int 类型
      in.nextToken(); // 读下一个词
      int m = (int) in.nval; // 读一个词的内容,转成 int 类型
	  // ...
      out.println(n, m); // 输出
    }
 
    out.flush(); // 将缓存区的内容刷到标准输出
 
    // 关流
    br.close();
    out.close();
  }
}

按行读

package main;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
 
public class Main {
  public static String line;
 
  public static void main(String[] args) throws IOException {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    while ((line = in.readLine()) != null) { // 按行读,输入流没有结束
	  // ...
      out.println("...");
    }
    out.flush();
    in.close();
    out.close();
  }
}

Go

  • 低效 IO:fmt.Scan*fmt.Print*
  • 高效 IO:bufio.NewScannerbufio.NewReaderSizebufio.NewWriterSizestrings.Builder

这道题 为例

低效 IO

当频繁地对少量数据读写时会占用 IO,造成性能问题

package main
 
import (
    "fmt"
)
 
func main() {
    var a, b int
    for {
        _, err := fmt.Scan(&a, &b) // 低效读
        if err != nil {
            break
        }
        fmt.Println(a + b) // 低效写
    }
}

高效读

Scanner

package main
 
import (
    "bufio"
    "os"
    "strings"
)
 
func main() {
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() { // input.Scan(): 读一行; input.Text(): 拿这行的内容
        line := input.Text() // string
        words := strings.Fields(line) // 分割这一行中的词,[]string
    }
}

指定缓冲区

bufio.NewScanner 缓冲区默认为 64*1024 字节,当一行数据多于缓冲区大小时会 panic,使用 Scanner.Buffer 方法进行设置,如:这道题

package main
 
import (
    "bufio"
    "os"
)
 
func main() {
	input := bufio.NewScanner(os.Stdin)
	// 缓冲区大小为8192字节,使用bufio.MaxScanTokenSize来设置最大扫描令牌的大小
	input.Buffer(make([]byte, 8192), bufio.MaxScanTokenSize)
    for input.Scan() {
        line := input.Text() // string,读一行
        // ...
    }
}

或者使用 bufio.NewReaderSize

package main
 
import (
    "bufio"
    "os"
)
 
func main() {
    reader := bufio.NewReaderSize(os.Stdin, 1 << 20) // 指定缓冲区大小
    for {
        line, _, err := reader.ReadLine() // 读一行, []byte
        if err != nil {
            break
        }
        // ...
    }
}

按词读

package main
 
import (
	"bufio"
	"fmt"
	"strings"
)
 
func main() {
	s := `1      2
       abc -28 uvw`
	in := bufio.NewScanner(strings.NewReader(s))
	in.Split(bufio.ScanWords)
	for in.Scan() {
		fmt.Println(in.Text())
	}
}

输出:

1
2
abc
-28
uvw

这道题 用到了按词读

高效写

带缓冲区的写

package main
 
import (
    "strings"
    "strconv"
    "bufio"
    "os"
    "fmt"
)
 
func main() {
    input := bufio.NewScanner(os.Stdin)
    output := bufio.NewWriterSize(os.Stdout, 1 << 20)
    for input.Scan() {
        arr := strings.Split(input.Text(), " ")
        a, _ := strconv.Atoi(arr[0])
        b, _ := strconv.Atoi(arr[1])
        fmt.Fprintln(output, a + b) // 先写出到缓冲区
        // 或
        // output.WriteString(strconv.Itoa(a + b))
        // output.WriteByte('\n')
    }
    output.Flush() // 将缓存区的内容刷到标准输出
}

一次写出

package main
 
import (
    "fmt"
    "strings"
    "strconv"
    "bufio"
    "os"
)
 
func main() {
    var builder strings.Builder
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        arr := strings.Split(input.Text(), " ")
        a, _ := strconv.Atoi(arr[0])
        b, _ := strconv.Atoi(arr[1])
        builder.WriteString(strconv.Itoa(a + b)) // 先写进 strings.Builder
        builder.WriteByte('\n')
    }
    fmt.Print(builder.String()) // 之后一次写出到标准输出
}

临时动态空间 VS. 全局静态空间

ACM 模式的题目要使用全局静态空间,每组测试都公用这些全局静态空间;不要用临时动态空间,如动态 newmake。因为测试平台算你代码的空间占用是算代码中所有申请空间的总和,不会管你有没有释放