ACM中使用JAVA v2.1
嚴明超
(Blog:mingchaoyan.blogbus.com
Email:[email protected])
0.前 言
文前聲明:本文只談java用於acm,且不推薦大家把java作爲自己的第一語言玩acm;
爲防止大家過多的把本應學習算法的寶貴時間浪費在學習語言上,同時也爲實現lcy一隊一java的要求;我根據自己用java的經驗,總結成文,希望可以拋磚引玉,幫助大家快速學會用java ac;
Java對熟悉c/c++的選手來說應該是似曾相識的,因爲它本身就是用相似c/c++結構設計的。一些安裝jdk,配置環境變量,以Main做類名等等這些我就不多說了。IDE推薦使用Eclipse,你可以在這裏下載。Eclipse是個平臺,所以你也可以裝個插件來跑c/c++,插件怎麼裝可以看看這裏(正規比賽一般都有Eclipse,沒vs,因爲vs是windows下的,正規比賽肯定不會用windows,爲什麼,你懂的);
說了點無關的話,進入正題,在這裏你將看到以下內容:1,輸入輸入,2.,大數/高精度,3,排序,4“STL”,5,其他
1. 輸入輸出
1.1 Scanner
輸入首推Scanner,輸出首推System.out.pritnln()/System.out.print();
這個學過java的一般都知道怎麼用,先以標準輸入爲參數new 一個Scanner,如果要讀入一個int,就用nextInt(),double 就用nextDouble,其他類型依次類推,另外可以用hasNextInt來判斷是否還有下一個。至於讀入以字符串用next(),讀入一行用nextLine();
附上A+B代碼
- import java.io.*;
- import java.util.*;
- public class Main {
- public static void main(String args[]) {
- Scanner cin = new Scanner(System.in);
- int a, b;
- while (cin.hasNextInt()) {
- a = cin.nextInt();
- b = cin.nextInt();
- System.out.println(a + b);
- }
- }
- }
但是用Scanner輸入就像cin那樣比較慢,當數據量一大會超時的,我記得有一個題目(忘了具體哪個題目),很直白的bfs就會超時,不排除你有很強的剪枝(雖然這樣的情況的確很少,一般Scanner用用足夠了),此時不得不用StreamTokenizer 和 PrintWriter ,請看看註釋(這裏格式比較亂,推薦大家把他copy到Eclipse下 ctrl+Shift + f (格式化代碼)了再讀):
- import java.io.*;
- public class Main {
- public static void main(String[] args) throws IOException
- // in.nextTokenizer可能拋出這個異常
- {
- StreamTokenizer in = new StreamTokenizer(new BufferedReader(
- new InputStreamReader(System.in)));
- // 這句是io流包裝來包裝去,記住就好;
- PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
- int a, b;
- while (in.nextToken() != StreamTokenizer.TT_EOF)
- // 用in.nextToken()讀下一個標記,StreamTokenizer.TT_EOF這個是個參數,就是EOF
- {
- a = (int) in.nval;
- // 讀進來的是double型的,所以要轉成int
- in.nextToken();
- // 讀入b值(a的值是在while循環中讀入的)
- b = (int) in.nval;
- out.println(a + b);
- }
- out.flush();
- // 刷新緩衝區,必須的,不然a+b會留在緩衝區
- }
- }
一般也用不到PrintWriter ,除非像這個輸出結果數據量很大,那用PrintWriter就會快很多了;
另外對於輸入,jdk 5.0以後可以用System.out.printf()這個格式化輸出了,你可以把它和printf聯繫起來,類似的,比如輸出小數點後3位System.out.printlnf("%.f%n",a);注意引號中的%n,這個是表示與平臺無關的換行,如果一定要用那個我們熟悉的/n,那就請在加一個/r,用/n/r,不然PE,搞不好還WA。。。這個問題是很著名的不同平臺回車和換行的尷尬,我就不多解釋了;
1.3 文件
還有一個問題,測試文件的輸入輸出。就像用C++時,只要簡單的在main下添加兩個freopen,java中也有類似的方法;
- FileInputStreamfin=newFileInputStream("/home/JCrzer/workspace/ACM/in.txt");
- PrintStreamfout=newPrintStream("/home/JCrazer/workspace/ACM/out.txt");
- System.setIn(fin);
- System.setOut(fout);
Windows要不"/"改成"//",或者索性改成"/",你懂的。。。。
2 大數/高精度
用java寫acm應該就是衝着這個來的吧^_^,java中有兩個類BigDecimal 和 BigInteger。看名字就知道他們的區別了
2.1 大數
以下是hdoj1002代碼,很直白的,不多解釋;
-
import java.math.*;
-
import java.util.*;
-
public class Main {
-
public static void main(String[] args) {
-
Scanner in = new Scanner(System.in);
-
int t = in.nextInt();
-
for (int i = 1; i <= t; ++i) {
-
if (i != 1)
-
System.out.println();
-
BigInteger a = in.nextBigInteger();
-
BigInteger b = in.nextBigInteger();
-
System.out.println("Case " + i + ":");
-
System.out.println(a + " + " + b + " = " + a.add(b));
-
}
-
}
-
}
- import java.math.*;
- import java.util.*;
- public class Main {
- public static void main(String[] args) {
- Scanner in = new Scanner(System.in);
- int t = in.nextInt();
- for (int i = 1; i <= t; ++i) {
- if (i != 1)
- System.out.println();
- BigInteger a = in.nextBigInteger();
- BigInteger b = in.nextBigInteger();
- System.out.println("Case " + i + ":");
- System.out.println(a + " + " + b + " = " + a.add(b));
- }
- }
- }
2.2 高精度
來看hdoj1753
- import java.math.BigDecimal;
- import java.util.Scanner;
- public class Main {
- public static void main(String[] args) {
- Scanner in = new Scanner(System.in);
- BigDecimal a, b;
- while (in.hasNext()) {
- a = in.nextBigDecimal();
- b = in.nextBigDecimal();
- System.out.println(a.add(b).stripTrailingZeros().toPlainString());
- }
- }
- }
因爲高精度加法結果的精度是取兩個加數中較大的那個,所以可能會出現後置0,這時就要通過stripTrailingZeros()把後置0去掉,又因爲,BigDecimal的toString()方法可能返回的是科學技術法表示的,所以要用toPlainString()轉成普遍計數法;
關於大數高精度其實還有很多問題,比如初始化構造,精度,比如舍入等等,這些零零碎碎的東西太多了,都舉個例子寫下會累死我的,我也不想把api中的內容copy來貼在這裏充字數,所以建議平時多查查api(在這裏查,中文版),還有有什麼需要留言吧~;
3排序
大數可能之前大家也敲過,排序可能是大家陌生的,因爲java寫排序和c/c++差別有點大;
最簡單的排序Arrays.sort(a),默認是按數組a從小到大排序的,如果是String就按字典序,而且對指定範圍的排序也已經有重載版本sort(Object[] a, int fromIndex, int toIndex) 其中和c++中類似,此方法排序時,範圍包括fromIndex,,不包括toIndex。但只是這種單一的排序方式顯然不能滿足我們變幻莫測排序要求。排序有兩種方法:
3.1 實現Comparable接口
這個比較常用,就是在設計Node這個類時(java中沒有struct關鍵詞,都要用class代替),實現Comparable這個接口,重寫其compareTo這個方法,在這個方法中規定你要的排序順序。如此在接下來的對Node實例數組排序時,就是按照你定義的排序順序來排的;比如:
- import java.util.Arrays;
- class Node implements Comparable {
- int a;
- Node(int a) {
- this.a = a;
- }
- @Override
- public int compareTo(Object arg0) {
- int a = ((Node) arg0).a;
- if (this.a < a)
- return -1;
- else if (this.a > a)
- return 1;
- return 0;
- //熟練了就可以犀利的寫成 return this.a-a; 但下文都用以上“規範清晰”的寫法
- }
- @Override
- public String toString() {
- return "" + a;
- }
- }
- public class Main {
- public static void main(String[] args) {
- Node[] node = new Node[4];
- node[0] = new Node(9);
- node[1] = new Node(8);
- node[2] = new Node(100);
- node[3] = new Node(0);
- Arrays.sort(node);
- System.out.println(Arrays.toString(node));
- }
- }
運行結果:
[0, 8, 9, 100]
說明3點:
1.在Node類中的toString是隻是我爲了打印運行結果而重寫的,與排序無關;
2.Node有多個屬性時,當然可以根據需要二級排序,三級排序等等;
3.這樣設計Node之後只能按照你定義的排序了,如果對一個Node要用不同的標準排序,請看下面:
3.2 使用Comparator比較器
使用Comparator就不像上面那樣比較死板了,你可以設計Cmp1,Cmp2來滿足你不同標準排序的要求
比如下面是我隨便舉的例子,標準1:對人員先按照名字升序,如果名字相同按照年齡升序 ,標準2:先對年齡升序,如果年齡相同就按姓名升序
- import java.util.Arrays;
- import java.util.Comparator;
- class Person {
- private String name;
- private int age;
- public Person(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return this.name;
- }
- public int getAge() {
- return this.age;
- }
- @Override
- public String toString() {
- // TODO Auto-generated method stub
- return "" + this.name + " " + this.age;
- }
- }
- class Cmp1 implements Comparator {
- @Override
- public int compare(Object arg0, Object arg1) {
- Person a = (Person) arg0;
- Person b = (Person) arg1;
- if (a.getName().compareTo(b.getName()) != 0)
- return a.getName().compareTo(b.getName());
- else {
- if (a.getAge() < b.getAge())
- return -1;
- else if (a.getAge() > b.getAge())
- return 1;
- else
- return 0;
- }
- }
- }
- class Cmp2 implements Comparator {
- @Override
- public int compare(Object arg0, Object arg1) {
- Person a = (Person) arg0;
- Person b = (Person) arg1;
- if (a.getAge() < b.getAge())
- return -1;
- else if (a.getAge() > b.getAge())
- return 1;
- else
- return a.getName().compareTo(b.getName());
- }
- }
- public class Main {
- public static void main(String[] args) {
- Person[] p = new Person[4];
- p[0] = new Person("ZZZ", 19);
- p[1] = new Person("AAA", 109);
- p[2] = new Person("AAA", 19);
- p[3] = new Person("YYY", 100);
- Arrays.sort(p, new Cmp1());
- System.out.println("Cmp1");
- System.out.println(Arrays.toString(p));
- Arrays.sort(p, new Cmp2());
- System.out.println("Cmp2");
- System.out.println(Arrays.toString(p));
- }
- }
運行結果;
Cmp1
[AAA 19, AAA 109, YYY 100, ZZZ 19]
Cmp2
[AAA 19, ZZZ 19, YYY 100, AAA 109]
這種方法在CMP這個類設計好後,要排序時直接在Arrays.sort()中new一個CMP實例就好了,是不是很像c++中sort,或者qsort。由於這個類只在排序時使用,所以也可以設計成匿名內部類(只對Cmp1舉例),代碼如下;
- import java.util.Arrays;
- import java.util.Comparator;
- class Person {
- private String name;
- private int age;
- public Person(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return this.name;
- }
- public int getAge() {
- return this.age;
- }
- @Override
- public String toString() {
- return "" + this.name + " " + this.age;
- }
- }
- public class Main {
- public static void main(String[] args) {
- Person[] p = new Person[4];
- p[0] = new Person("ZZZ", 19);
- p[1] = new Person("AAA", 109);
- p[2] = new Person("AAA", 19);
- p[3] = new Person("YYY", 100);
- Arrays.sort(p, new Comparator<Object>() {
- @Override
- public int compare(Object arg0, Object arg1) {
- Person a = (Person) arg0;
- Person b = (Person) arg1;
- if (a.getName().compareTo(b.getName()) != 0)
- return a.getName().compareTo(b.getName());
- else {
- if (a.getAge() < b.getAge())
- return -1;
- else if (a.getAge() > b.getAge())
- return 1;
- else
- return 0;
- }
- }
- });
- System.out.println(Arrays.toString(p));
- }
- }
匿名內部類是沒有名字的內部類,你可以理解我把CMP這個類設計在了Arrays.sort()這個方法的參數中。你若java中這種機制不是很理解,就用前面那種吧。
4 “STL”
之所以對STL加引號,是因爲JAVA中似乎不叫標準模板庫,不過這個只是稱呼,你知道我是在說這個就好了。很多前輩都提出了“STL傷代碼能力論”我只是把它對應C++的那些列出來,用不用你看着辦吧:
4.1 Sting 和StringBuffer
關於String我只想說這個是常量,創建了以後永遠不會變的,想要變就用StringBuffer;這個有很多意想不到的方法,比如split方法可以根據給定的字符拆分字符串,方便吧,自己處理會麻煩死的還容易出錯,再比如這兩個都支持正則,正則的強大不需要我解釋,另外還有很多,查api去吧~~~
4.2 List接口
就是表,可以用LinkedList類(可以理解鏈表實現所以增刪比較快),ArrayList類(理解爲數組實現訪問比較快),當然還有大家很熟悉的Vector類;
創建 List ll = new LinkedList();(你也可以LinkedList ll = new LinkedList();但推薦使用前者,因爲在創建對象是,使用接口的引用指向對象是一個好習慣~)
方法: add(e), get(int index), clear(),isEmpty(),size()。。。(方法太多了,C++中實現的方法JAVA中差不多都有增無減地實現了,真的不想一一列出來,還是那句話,查api吧,真不行就留言)
JAVA集合框架中很多方法都是通用的,下面的有些方法就不再列舉了;
4.3 Map接口
映射,有Hashtable類(Hashtable的同步版本),LinkedHashMap類(按元素插入順序排序),HashMap類(比較常用),TreeMap類(按元素的自然順序排序,這個的存儲方式是傳說中的紅黑樹~)可以實現;使用put方法添加元素,get(key)方法獲得對應key的值,containsKey(key)方法判斷是否已經存在此映射;
4.4 Set接口
集合,有HashSet類(沒有排序),LinkedHashSet類(按插入順序排序),TreeSet類(按自然序排序)可以實現;
4.5 Queue 接口
隊列,用實現List的那些類實現好了,offer方法入隊,poll方法取隊頭並扔掉頭(若不想扔掉用peek方法);
4.6 PriorityQueue 類
優先隊列,注意這個是類,所以創建時一定要PriorityQueue qp = new PriotityQueue();方法和Queue類似;
4.7 Stack類
棧,注意這個也是類,push壓入棧,poll取棧頂並扔掉(同樣peek取頂不扔掉);
最後舉一個例子吧HDOJ1873(這個例子可以看到這些“玩具”的數組怎麼創建)
- import java.io.*;
- import java.util.PriorityQueue;
- public class Main {
- public static class Patient implements Comparable {
- int id, priority;
- public Patient(int id, int priority) {
- this.id = id;
- this.priority = priority;
- }
- @Override
- public int compareTo(Object arg0) {
- // 設置病人的優先級
- int priority = ((Patient) arg0).priority;
- int id = ((Patient) arg0).id;
- if (this.priority > priority)
- return -1;
- else if (this.priority < priority)
- return 1;
- else
- return this.id - id;
- }
- }
- public static void main(String[] args) throws IOException {
- StreamTokenizer in = new StreamTokenizer(new BufferedReader(
- new InputStreamReader(System.in)));
- while (in.nextToken() != StreamTokenizer.TT_EOF) {
- int n = (int) in.nval;
- int id = 1;
- PriorityQueue<Patient>[] pq = new PriorityQueue[3];
- // PriorityQueue<Patient> 泛型,new PriorityQueue[3]後面就不要加<Patient>了
- for (int i = 0; i < 3; ++i) {
- pq[i] = new PriorityQueue<Patient>();
- }
- for (int i = 0; i < n; ++i) {
- in.nextToken();
- String flag = in.sval;
- if (flag.equals("OUT")) {
- // String的等值比較,不要用==呀!!!!!!!!
- in.nextToken();
- int doctor = (int) in.nval;
- if (pq[doctor - 1].isEmpty())
- // 是否爲空
- System.out.println("EMPTY");
- else
- System.out.println(pq[doctor - 1].poll().id);
- // 取隊頭並扔掉
- } else {
- in.nextToken();
- int doctor = (int) in.nval;
- in.nextToken();
- int priority = (int) in.nval;
- pq[doctor - 1].offer(new Patient(id++, priority));
- }
- }
- }
- }
- }
5 其他
想不好再寫什麼,看大家需要吧~
關於傳值問題:
Java中對象(強調8種基本數據類型除外)一定是引用傳遞的,所以當你不需要引用傳遞時,就拷貝一份副本傳進去,當然拷貝可以用clone(),具體寫法看以下例子:
-
public class Main {
-
public static class Point implements Cloneable{
-
int x, y;
-
public Point(int x, int y) {
-
this.x = x;
-
this.y = y;
-
}
-
@Override
-
protected Object clone() throws CloneNotSupportedException {
-
return super.clone();
-
}
-
@Override
-
public String toString() {
-
return "("+x+","+y+")";
-
}
-
}
-
static void fun(Point p) {
-
p.x += 1;
-
p.y += 1;
-
}
-
public static void main(String[] args) throws CloneNotSupportedException {
-
Point p1 = new Point(9,8);
-
System.out.print("p1:");
-
System.out.println(p1);
-
//p2 is a copy of p1
-
Point p2 = (Point) p1.clone();
-
fun(p2);
-
System.out.print("yeah~ if you convert p1's clone, p1 won't change");
-
System.out.println(p1);
-
System.out.print("if you convert p1 directly, p1 will change in main()");
-
fun(p1);
-
System.out.println(p1);
-
}
-
}
- public class Main {
- public static class Point implements Cloneable{
- int x, y;
- public Point(int x, int y) {
- this.x = x;
- this.y = y;
- }
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- @Override
- public String toString() {
- return "("+x+","+y+")";
- }
- }
- static void fun(Point p) {
- p.x += 1;
- p.y += 1;
- }
- public static void main(String[] args) throws CloneNotSupportedException {
- Point p1 = new Point(9,8);
- System.out.print("p1:");
- System.out.println(p1);
- //p2 is a copy of p1
- Point p2 = (Point) p1.clone();
- fun(p2);
- System.out.print("yeah~ if you convert p1's clone, p1 won't change");
- System.out.println(p1);
- System.out.print("if you convert p1 directly, p1 will change in main()");
- fun(p1);
- System.out.println(p1);
- }
- }
6後記
本文給出一些用java做acm的基本用法,一般不是“純java選手”,學會上面這些應付應付比賽中“突如其來”的大數高精度之類java有明顯優勢的題目足夠了。但是不要迷戀java,java只是一種語言,語言是規則,算法是思想;
最後再次指出本文只談java用於acm,且不推薦大家把java作爲自己的第一語言玩acm,歡迎拍磚~~