
1. 為什么我建議每個編程新手都去POJ走一遍第一次聽說POJPKU JudgeOnline的時候我還以為是什么新出的編程框架。后來才知道這是國內(nèi)最早一批在線評測系統(tǒng)之一圈內(nèi)人習(xí)慣叫它“北大題庫”。它不像現(xiàn)在那些花里胡哨的刷題平臺有積分商城、有排行榜動畫、有AI提示POJ的界面樸素得像是上世紀(jì)的產(chǎn)物但正是這種樸素讓它成了無數(shù)ACM選手起步的地方。POJ的核心價值在于它用最直接的方式告訴你你的代碼到底對不對。你提交一段程序它拿后臺準(zhǔn)備好的測試數(shù)據(jù)跑一遍結(jié)果只有幾種——ACAccepted通過、WAWrong Answer答案錯誤、TLETime Limit Exceeded超時、RERuntime Error運行錯誤、CECompile Error編譯錯誤。沒有商量余地沒有部分分對了就是對了錯了就是錯了。這種“非黑即白”的反饋機制對新手來說反而是最高效的學(xué)習(xí)方式。這篇文章適合誰看如果你剛學(xué)完C或C的基礎(chǔ)語法知道什么是變量、循環(huán)、數(shù)組、函數(shù)但還沒在任何一個在線評測系統(tǒng)上完整做過一道題那這篇就是寫給你的。我會從注冊賬號開始一步步帶你走到AC第一題中間踩過的坑、繞過的彎我都會提前告訴你。整個過程不需要你有多高的編程天賦只需要你愿意動手敲代碼、愿意看報錯信息、愿意改。另外說一句POJ上的題目大多是英文描述這可能會讓一部分新手發(fā)怵。但相信我編程競賽的英文題面翻來覆去就那么些詞匯做上十來道之后你讀題的速度會比你想象中快得多。而且這種“硬啃英文題面”的能力在后面參加各類編程競賽或者面試時會變成你的隱性優(yōu)勢。2. 注冊之前先把這些準(zhǔn)備工作做扎實2.1 選對瀏覽器和網(wǎng)絡(luò)環(huán)境POJ的網(wǎng)站架構(gòu)比較老舊對現(xiàn)代瀏覽器的兼容性只能說“能用”。我實測下來Chrome和Edge都能正常訪問和提交但偶爾會出現(xiàn)頁面樣式錯亂的情況這不影響功能刷新一下就好。不建議用手機瀏覽器操作POJ的頁面沒有做移動端適配在手機上點按鈕會非常痛苦。網(wǎng)絡(luò)方面POJ的服務(wù)器在國內(nèi)訪問速度還算可以但提交代碼后等待評測結(jié)果的時間有時候會比較長尤其是晚上高峰期。這不是你的問題是服務(wù)器負載的問題。如果提交后狀態(tài)一直顯示“Pending”或者“Judging”耐心等一會兒不要反復(fù)提交同一份代碼那樣只會讓隊列更長。注意POJ不支持HTTPS訪問時瀏覽器可能會提示“不安全”這是正?,F(xiàn)象直接繼續(xù)訪問即可。不要因為這個就放棄它只是一個老舊的評測系統(tǒng)不涉及任何支付或隱私信息。2.2 想清楚你要用哪門語言POJ支持的語言包括C、C、Java、Pascal等但最主流的是C。我強烈建議新手直接用C原因有三第一POJ上絕大多數(shù)題目的題解和討論都是基于C的你遇到問題去搜的時候C的資料最多第二C的STL標(biāo)準(zhǔn)模板庫在刷題時非常實用vector、map、set、sort這些工具能幫你省下大量手寫代碼的時間第三C的編譯錯誤信息相對Java來說更直觀新手更容易看懂哪里出了問題。如果你只學(xué)過C那也完全沒問題POJ上用C提交的人很多。但你要做好心理準(zhǔn)備C語言沒有STL很多數(shù)據(jù)結(jié)構(gòu)需要自己手寫比如動態(tài)數(shù)組、隊列、棧。這本身也是一種鍛煉只是前期上手會慢一些。Java選手在POJ上要注意POJ對Java的時限有時候會比較緊同樣的算法Java跑出來可能比C慢好幾倍導(dǎo)致TLE。所以如果你用Java盡量選擇時間復(fù)雜度更優(yōu)的寫法并且注意輸入輸出的效率用BufferedReader和StringTokenizer代替Scanner。2.3 準(zhǔn)備好本地編譯環(huán)境雖然POJ是在線評測但你不可能每次都在網(wǎng)頁上直接寫代碼。你需要一個本地的編輯器或IDE來寫代碼、調(diào)試、測試確認沒問題了再復(fù)制到POJ上提交。新手我推薦用Dev-C或者Code::Blocks這兩個都是輕量級的C/C IDE安裝簡單自帶編譯器開箱即用。如果你已經(jīng)習(xí)慣了VS Code那也可以但需要自己配置一下編譯環(huán)境對純新手來說稍微麻煩一點。本地環(huán)境最重要的作用是你可以自己造測試數(shù)據(jù)來驗證代碼。POJ上的題目通常會給一兩個樣例輸入輸出但樣例過了不代表你的代碼就對了。很多新手栽跟頭就栽在“樣例全過提交WA”上。所以本地環(huán)境一定要能跑代碼能自己輸入數(shù)據(jù)看輸出。3. 手把手完成POJ賬號注冊與登錄3.1 找到正確的注冊入口打開POJ的主頁之后你會看到一個非常樸素的頁面左側(cè)是導(dǎo)航欄右側(cè)是題目列表。注冊入口在導(dǎo)航欄的“Register”鏈接點進去之后會看到一個表單需要填寫以下信息User ID這是你的登錄名只能用字母和數(shù)字不能有空格和特殊字符。建議取一個你好記的名字因為以后登錄都要用。Password密碼注意POJ的密碼是區(qū)分大小寫的。Verify Password再輸一遍密碼。Nick Name昵稱這個可以隨便填會顯示在你的提交記錄和排名里。Email郵箱填一個你能記住的就行POJ不會發(fā)驗證郵件但如果你忘了密碼這是找回的唯一途徑。School學(xué)??梢蕴钜部梢圆惶畈挥绊懯褂?。填完之后點“Submit”按鈕如果一切正常你會看到注冊成功的提示。如果提示“User ID already exists”說明你取的名字被別人占了換一個再試。提示POJ的注冊沒有郵箱驗證環(huán)節(jié)所以理論上你可以用任意郵箱注冊。但建議填真實郵箱萬一以后需要找回密碼不至于抓瞎。3.2 登錄后的第一件事熟悉界面登錄成功之后你會回到主頁這時候?qū)Ш綑跁喑鰩讉€選項比如“Status”提交狀態(tài)、“Submit”提交代碼、“Logout”退出登錄。我建議你先花五分鐘把界面點一遍知道每個按鈕是干什么的。最重要的是“Problems”頁面這里列出了POJ上所有的題目。每道題有一個數(shù)字編號比如1000、1001、1002……編號越小通常題目越基礎(chǔ)。新手就從1000題開始這道題叫“AB Problem”幾乎是所有在線評測系統(tǒng)的“Hello World”。在題目列表頁面你可以看到每道題的標(biāo)題、通過率、總提交次數(shù)等信息。通過率這個數(shù)據(jù)很有參考價值如果一道題的通過率特別低比如低于20%那說明這道題要么很難要么有坑新手暫時不要碰。1000題的通過率通常在50%以上適合練手。3.3 賬號安全與使用習(xí)慣POJ作為一個老系統(tǒng)沒有兩步驗證、沒有登錄設(shè)備管理這些現(xiàn)代功能。所以你的賬號安全完全依賴于密碼強度。建議設(shè)置一個你其他網(wǎng)站沒用過的密碼避免被撞庫。另外POJ的會話Session保持時間有限如果你長時間不操作可能會自動退出登錄。這時候你正在寫的代碼如果沒保存就會丟失。所以養(yǎng)成習(xí)慣在POJ的提交框里寫代碼時先在本地寫好、測試好再復(fù)制過去。不要在網(wǎng)頁的提交框里直接寫大段代碼萬一掉線就全沒了。4. 從讀題到寫出第一份可提交的代碼4.1 以POJ 1000為例讀懂題面POJ 1000的題面非常短翻譯過來就是輸入兩個整數(shù)a和b輸出它們的和。題面會給出樣例輸入和樣例輸出比如輸入“1 2”輸出“3”。這道題的目的不是考你算法而是讓你走通整個流程讀題、寫代碼、本地測試、提交、看到AC。但即使是這么簡單的題新手也容易犯幾個錯誤。第一個錯誤是沒看清輸入輸出格式。POJ的題目對輸出格式要求極其嚴(yán)格多一個空格、少一個換行都會判WA。比如這道題輸出就是一個整數(shù)后面跟一個換行。你不能輸出“The sum is 3”這種多余的文字也不能輸出“3 ”后面多一個空格。第二個錯誤是沒注意數(shù)據(jù)范圍。雖然1000題的數(shù)據(jù)范圍很小但養(yǎng)成看數(shù)據(jù)范圍的習(xí)慣很重要。如果題目說a和b的范圍是-10000到10000那你用int就足夠了。但如果范圍是10的18次方那就得用long long。這個習(xí)慣從第一題就開始培養(yǎng)后面會少踩很多坑。4.2 寫代碼從框架到細節(jié)C提交到POJ的代碼標(biāo)準(zhǔn)框架是這樣的#include iostream using namespace std; int main() { int a, b; cin a b; cout a b endl; return 0; }就這么幾行。但我要解釋一下每一部分為什么這么寫#include iostream引入輸入輸出流庫cin和cout都在這里面。using namespace std;讓std命名空間里的名字可以直接用不用寫std::cin、std::cout。新手階段建議加上省事。int main()主函數(shù)程序從這里開始執(zhí)行。POJ要求提交的代碼必須有main函數(shù)。cin a b;從標(biāo)準(zhǔn)輸入讀取兩個整數(shù)。cin會自動跳過空格和換行所以輸入“1 2”或者“1\n2”都能正確讀取。cout a b endl;輸出ab的值endl會輸出一個換行符并刷新緩沖區(qū)。return 0;告訴操作系統(tǒng)程序正常結(jié)束。雖然不寫這行大多數(shù)編譯器也會自動加上但養(yǎng)成寫的習(xí)慣。注意有些老教程會建議用scanf和printf代替cin和cout因為前者更快。對于1000題這種輸入量極小的題目兩者沒有區(qū)別。但到了后面輸入量大的題目cin可能會成為性能瓶頸。新手階段先用cin/cout等遇到TLE了再考慮換scanf/printf或者加上ios::sync_with_stdio(false);來加速。4.3 本地測試別急著提交代碼寫完之后先在本地跑一下。用題目給的樣例輸入“1 2”測試看輸出是不是“3”。然后再自己造幾組數(shù)據(jù)比如輸入“-5 3”期望輸出“-2”輸入“0 0”期望輸出“0”輸入“1000000 2000000”期望輸出“3000000”。這些邊界情況都過了再提交。很多新手嫌麻煩樣例過了就直接提交結(jié)果WA了又回來改來回折騰。其實本地多花兩分鐘測試能省下后面十分鐘的調(diào)試時間。而且自己造數(shù)據(jù)的過程本身就是在訓(xùn)練你考慮邊界情況的能力這個能力在編程競賽里比什么都重要。5. 提交代碼與解讀評測結(jié)果5.1 提交操作的具體步驟在POJ上提交代碼的流程是這樣的先進入題目頁面比如1000題在頁面下方會看到一個下拉框選擇語言比如“C”然后是一個大文本框用來粘貼代碼最后點“Submit”按鈕。粘貼代碼的時候要注意不要把你本地的行號也復(fù)制進去有些編輯器會顯示行號復(fù)制的時候會把行號一起復(fù)制。POJ的編譯器不認識行號會直接報CE。另外代碼里不要有中文注釋雖然C標(biāo)準(zhǔn)支持中文注釋但POJ的編譯器環(huán)境比較老有時候中文注釋會導(dǎo)致編譯錯誤。保險起見注釋用英文寫或者干脆不寫注釋。提交之后頁面會跳轉(zhuǎn)到“Status”頁面你會看到你剛才的提交記錄狀態(tài)一開始是“Pending”或者“Judging”表示正在評測。等幾秒鐘到幾十秒鐘狀態(tài)會變成最終結(jié)果。5.2 各種評測結(jié)果的含義與應(yīng)對POJ的評測結(jié)果有好幾種新手最常見的是下面這幾種結(jié)果縮寫全稱含義新手常見原因ACAccepted通過代碼正確且效率達標(biāo)WAWrong Answer答案錯誤邏輯錯誤、邊界沒考慮、輸出格式不對TLETime Limit Exceeded超時算法效率太低、死循環(huán)RERuntime Error運行錯誤數(shù)組越界、除以零、棧溢出CECompile Error編譯錯誤語法錯誤、頭文件缺失、中文符號PEPresentation Error格式錯誤輸出多了或少了空格/換行看到AC當(dāng)然是最開心的但我想說的是看到WA、TLE、RE、CE才是你真正開始學(xué)習(xí)的時候。每一次錯誤都是一次精準(zhǔn)的反饋告訴你哪里有問題。下面我逐個說一下應(yīng)對思路。WA是最常見的。遇到WA先檢查輸出格式是不是多輸出了什么提示文字是不是少了一個換行然后檢查邊界條件如果輸入是0會怎樣如果輸入是負數(shù)會怎樣如果輸入是最大值會怎樣最后檢查邏輯把你的代碼在腦子里跑一遍或者用調(diào)試器單步跟蹤看看變量的值是不是符合預(yù)期。TLE說明你的算法太慢了。對于1000題這種題目TLE幾乎不可能發(fā)生除非你寫了死循環(huán)。但到了后面比如題目要求處理10萬個數(shù)據(jù)你用雙重循環(huán)就是O(n2)肯定會TLE。這時候就要想更高效的算法比如排序、二分查找、動態(tài)規(guī)劃等。RE通常是數(shù)組開小了。比如題目說最多有1000個數(shù)據(jù)你數(shù)組開了100那讀到第101個數(shù)據(jù)時就越界了。POJ對數(shù)組越界的檢查不像本地調(diào)試那么嚴(yán)格有時候越界了不報錯但結(jié)果是錯的判WA有時候直接崩潰判RE。所以數(shù)組大小一定要按題目給的最大值來開甚至可以多開幾個作為緩沖。CE就是語法錯誤。POJ的編譯器版本比較老對C標(biāo)準(zhǔn)的支持可能和你的本地編譯器不一樣。比如你在本地用C17的特性POJ可能只支持C98就會CE。遇到CE看編譯器給出的錯誤信息通常會告訴你哪一行出了什么問題。5.3 第一次AC之后該做什么看到綠色的“Accepted”出現(xiàn)在屏幕上恭喜你你已經(jīng)完成了從注冊到AC的完整閉環(huán)。但這只是開始不是結(jié)束。我建議你做完1000題之后緊接著做1001、1002、1003這幾道題。它們都是基礎(chǔ)題但每道題考察的點不一樣1001涉及浮點數(shù)輸出格式1002涉及大數(shù)運算1003涉及遞歸或遞推。把這些題都做一遍你對POJ的提交機制、評測規(guī)則、常見坑點就會有比較全面的認識。另外每次AC之后去看看“Status”頁面里別人的提交。POJ允許你查看別人AC的代碼在提交記錄里點“View”或者類似的鏈接。看看別人是怎么寫的尤其是那些代碼短、運行時間短的提交你能從中學(xué)到很多技巧。比如同樣的題目有人寫了50行有人寫了10行那10行的寫法往往利用了STL或者更巧妙的數(shù)學(xué)方法。6. 新手最容易踩的坑與排查技巧6.1 輸入輸出的那些“隱形陷阱”POJ的題目輸入格式千奇百怪有的是單組數(shù)據(jù)有的是多組數(shù)據(jù)直到文件結(jié)束EOF有的是先給一個整數(shù)T表示接下來有T組數(shù)據(jù)。新手最容易搞錯的就是多組數(shù)據(jù)的處理。比如題目說“輸入包含多組測試數(shù)據(jù)每組數(shù)據(jù)占一行包含兩個整數(shù)a和b直到文件結(jié)束”那你的代碼就不能只讀一次而要用循環(huán)#include iostream using namespace std; int main() { int a, b; while (cin a b) { cout a b endl; } return 0; }while (cin a b)這個寫法利用了cin在讀到文件結(jié)束時會返回false的特性。這是C處理多組輸入的經(jīng)典寫法一定要記住。還有一種情況是題目說“第一行是一個整數(shù)T表示有T組數(shù)據(jù)”那你的代碼就要先讀T再循環(huán)T次#include iostream using namespace std; int main() { int T; cin T; while (T--) { int a, b; cin a b; cout a b endl; } return 0; }這兩種模式覆蓋了POJ上90%以上的輸入格式。剩下的就是一些特殊情況比如輸入以0 0結(jié)束那就在循環(huán)里判斷if (a 0 b 0) break;。輸出格式方面最常見的要求是“每組數(shù)據(jù)輸出一行”那你就用endl或者\n換行。如果題目說“兩組數(shù)據(jù)之間輸出一個空行”那就要在每組輸出之后多輸出一個換行但注意最后一組后面不要多輸出。這種細節(jié)題面里都會寫清楚讀題的時候用筆把輸出要求劃出來能避免很多WA。6.2 數(shù)組大小與數(shù)據(jù)類型的經(jīng)驗法則數(shù)組開多大我的經(jīng)驗法則是看題目給的數(shù)據(jù)范圍然后在這個范圍的基礎(chǔ)上乘以2到10。比如題目說“1 n 1000”那數(shù)組就開1005或者1010。多開幾個不會浪費多少內(nèi)存但能有效防止越界。數(shù)據(jù)類型怎么選記住幾個關(guān)鍵閾值int大約能存到2×10^9也就是20億左右。如果題目數(shù)據(jù)范圍在10^9以內(nèi)用int沒問題。long long大約能存到9×10^18。如果題目數(shù)據(jù)范圍超過10^9或者涉及乘法運算比如兩個10^5的數(shù)相乘就要用long long。double浮點數(shù)精度大約15位有效數(shù)字。如果題目要求保留幾位小數(shù)用double通常夠用。但要注意浮點數(shù)的比較不能用要用一個很小的誤差值比如1e-8來判斷。提示POJ上有一類題目專門考察大數(shù)運算比如兩個幾百位的整數(shù)相加。這種題目用int和long long都存不下需要用字符串或數(shù)組來模擬手工運算。新手遇到這種題可以先跳過等基礎(chǔ)扎實了再回來做。6.3 調(diào)試技巧從WA到AC的排查路徑遇到WA不要慌按下面的順序排查檢查輸出格式把題目要求的輸出格式和你的實際輸出逐字對比。特別注意空格、換行、大小寫。測試邊界數(shù)據(jù)輸入最小值、最大值、0、負數(shù)看輸出是否符合預(yù)期。檢查循環(huán)邊界for循環(huán)是從0開始還是從1開始是 n還是 n這種off-by-one錯誤是WA的重災(zāi)區(qū)。檢查變量初始化局部變量不會自動初始化為0如果你用了一個沒初始化的變量它的值是隨機的結(jié)果自然也是隨機的。用printf大法在代碼的關(guān)鍵位置插入輸出語句把中間變量的值打印出來看看是不是和你預(yù)期的一致。確認沒問題后再把這些調(diào)試語句刪掉。如果以上都檢查了還是WA那就去POJ的“Discuss”板塊看看。每道題都有一個討論區(qū)里面有很多人分享的測試數(shù)據(jù)和易錯點。有時候你會發(fā)現(xiàn)原來題目有一個隱藏條件你沒注意到比如“輸入可能包含多個空格”或者“輸出要求四舍五入”。6.4 常見問題速查表問題現(xiàn)象可能原因解決方法提交后一直Pending服務(wù)器繁忙等待不要重復(fù)提交CE且提示找不到頭文件用了POJ不支持的庫換用標(biāo)準(zhǔn)庫如iostream、cstdioCE且提示中文符號代碼里有中文標(biāo)點檢查分號、括號、逗號是否為英文WA且樣例全過邊界條件沒考慮測試0、負數(shù)、最大值TLE且數(shù)據(jù)量不大死循環(huán)檢查while和for的終止條件RE且數(shù)組不大除以零或棧溢出檢查除法運算和遞歸深度輸出結(jié)果正確但PE多了空格或換行嚴(yán)格按題目要求輸出7. 從AC第一題到持續(xù)刷題的進階路線7.1 建立自己的刷題節(jié)奏AC了第一題之后最怕的就是“三天打魚兩天曬網(wǎng)”。刷題這件事連續(xù)性比強度更重要。我的建議是每天至少做一道題哪怕這道題很簡單。如果某天實在沒時間至少把前一天做過的題再提交一遍保持手感。POJ的題目編號從1000開始前面的題目大多是基礎(chǔ)題適合用來建立信心。我建議新手按順序做1000到1010這十道題它們覆蓋了輸入輸出、循環(huán)、條件判斷、數(shù)組、字符串、排序等基礎(chǔ)知識點。做完這十道你對POJ的脾氣就摸得差不多了。然后可以跳到一些經(jīng)典題比如12073n1問題、1503大數(shù)加法、1656棋盤翻轉(zhuǎn)、2000金幣問題。這些題在POJ上通過率比較高而且每道題都有明確的考察點適合用來鞏固特定知識點。7.2 利用POJ的討論區(qū)和提交記錄POJ的“Discuss”板塊是一個被很多人忽視的寶藏。每道題的討論區(qū)里有人分享測試數(shù)據(jù)有人指出易錯點有人貼出精簡的代碼。新手遇到卡殼的時候去討論區(qū)翻一翻往往能找到突破口。但要注意不要直接抄別人的代碼。抄代碼一時爽但你的能力不會因此提升。正確的做法是看別人的思路理解為什么這么寫然后自己動手實現(xiàn)一遍。如果實在想不出來看完別人的代碼后關(guān)掉頁面憑記憶自己寫一遍。這個過程比單純抄代碼痛苦但效果天差地別。提交記錄也是學(xué)習(xí)資源。在“Status”頁面你可以看到所有人的提交包括那些AC的代碼??纯磩e人AC的代碼用了什么技巧運行時間是多少內(nèi)存占用是多少。同樣的題目如果你的代碼運行時間是100ms別人是10ms那說明別人的算法更優(yōu)你可以去研究一下他是怎么做的。7.3 從POJ延伸到其他平臺POJ是一個很好的起點但它不是終點。當(dāng)你AC了二三十道題之后可以嘗試去其他在線評測系統(tǒng)看看比如LeetCode、Codeforces、洛谷等。每個平臺都有自己的特色LeetCode偏重面試算法題Codeforces偏重競賽洛谷偏重中文題面和社區(qū)氛圍。但無論去哪個平臺你在POJ上培養(yǎng)起來的能力都是通用的讀題能力、邊界分析能力、調(diào)試能力、算法設(shè)計能力。這些能力不會因為換了平臺就失效反而會在不同的題目風(fēng)格中得到強化。我個人在實際操作中的體會是POJ最適合用來打基礎(chǔ)因為它的反饋直接、題目經(jīng)典、沒有太多花哨的東西。你在POJ上養(yǎng)成的嚴(yán)謹(jǐn)習(xí)慣到了其他平臺會變成你的競爭優(yōu)勢。很多新手一上來就去刷LeetCode的難題結(jié)果被各種WA和TLE打擊得信心全無。不如先在POJ上把基礎(chǔ)打牢再去挑戰(zhàn)更復(fù)雜的題目。最后再分享一個小技巧每次AC一道題之后不要急著做下一道花兩分鐘把這道題的代碼再看一遍想想有沒有可以優(yōu)化的地方有沒有更簡潔的寫法。這個復(fù)盤的習(xí)慣比多做十道題更有價值。