少打頭文件
少打using namespace std;
命名沖突,全局變量與局部變量命名一致,導致使用的值不是期望值
邊讀邊寫,導致改后讀,覆蓋寫入的值
長整數移位溢出,1<<63是錯誤的,應該寫成1ll<<63
循環變量打錯,i打成j? 犯錯次數加一
乘法溢出導致的,數組下標正溢出或負溢出
dfs時,開頭沒有vis[start]=1;
bfs時,狀態維數開少
bfs時,少寫了vis[node]=1或者少寫了Q.pop導致的MLE
解同余方程時,不先約去GCD的話,解的距離就是a/GCD或b/GCD,先約了的話,那么需要原方程的值的時候就需要帶入a,而不是已經改變的a/GCD
做exgcd的題目一定要小心ans%mod==0的情況
(N-ans)/y+1 如果ans==0,則1不應該加
公式不能只考慮第一個元素,其他元素也要全部列出來
單調隊列總是忘了取元素,直接把隊列中的下標拿出來用
vector的resize不會釋放內存,之前的值都還在...
二重循環里容易把=寫成+=
?INF值不夠大
char數組放了過大的值
char數組結尾不是'\0'
當要改變一系列聲明變量的類型時。。。如果不小心在回車換行時,把大數據類型改成了小數據類型,則會發生溢出。或者ub
比如int-->bool 結果不斷加一他還是一
?
然后就是下標i,j容易弄混,在內循環容易仍然使用i,或者使用常量
?
然后平常容易邏輯寫反比如while(a!=NULL&&b!=NULL)和while(!a&&!b)這兩個看起來很像,但是邏輯上是相反的
?
然后就是編程范式。。。也叫循環不變式,這個一定要想好,是最前面初始化推最后,還是先計算最后考慮結尾
?
?數組名稱寫錯,傳的參數一直不變,或者臨時參數一直沒有還原
?
不能整除導致的加一和(int)(divison expr+0.5)是不一樣的。。。。
?
還有一道雪崩的大數題目:http://www.cnblogs.com/linkzijun/p/6106697.html
?
https://www.jisuanke.com/article/po3pm3e6
?下面調試程序的技巧轉自計蒜客
作者介紹:計蒜客研發工程師,畢業于西南科技大學,曾獲 ACM/ICPC 亞洲區預賽金獎。
代碼能力是我們在程序設計競賽中常常談到的一種能力,是指選手把算法用代碼準確地實現的能力。
2005 年,Comars 曾經給代碼能力作過一個比較準確的定義:如果我?150150?行以內的題目,1Y(提交一次就正確通過,Y 是指 Yes)率非常高,并且保持穩定;而當代碼長度超過?150150?行以后,1Y 率就開始急速下降了。如果我們畫出一條代碼行數與 1Y 率之間的關系的曲線,150150?行就是一個轉折點。我們不妨認為,150150?行就是 Comars 當時的代碼能力。一年以后,經過努力,Comars 把代碼能力提高到了?250250?行,也就意味著他通常可以無錯誤地寫出一份?250250?行的代碼。
當然,想要把代碼能力提高到?150150?行并非是一個簡單的事情。在代碼能力不夠強的時候,就需要有足夠的調試(debug)能力了。結合我的比賽經歷和平時訓練的經驗,給大家分享一些程序設計競賽題目 debug 的技巧。
在開始 debug 之前,要先在腦海中過一遍思路,必須保證自己有一個清晰的算法思路和一個正確的算法(至少自己要相信它是正確的)。一定要有清晰的思路,不然寫出來的代碼可能連自己都看不懂;而基于一個錯誤算法的 debug 是毫無意義的。當你確認了上面兩件事情以后,才有必要開始 debug。
1) 順著你的思路仔細閱讀自己的代碼兩到三遍,注意是仔細,要一句一句地讀。核對你的代碼和你的思路是否一致,不要放過任何一個小細節。如果遇到拿不準的地方,立刻停下來仔細想想,直到想清楚以后再繼續。在這個過程中,往往可以找到很多低級錯誤,尤其是對于代碼能力不太好的同學,比如——變量打錯、代碼寫錯位置、變量賦值錯誤等等。靜態閱讀代碼的效率是非常高的,因為往往讀一份自己寫出的代碼的時間遠小于寫的時間——既然都已經花了那么多時間寫出來了,何必還在乎這點時間多讀幾遍呢。
2) 如果經過上面的過程還沒有找到程序中的錯誤,或者找到了一些問題但是程序的結果還是不對,這時我們就要通過運行程序來 debug。根據我以往參加競賽的經驗,經過上面的過程基本上可以解決一半以上的問題。如果一定要走到這一步,很可能已經給自己挖了一個巨大的坑。想要通過運行程序來 debug,第一步是需要拿到一組能使得程序出錯的數據,拿到錯誤數據以后,debug 就成功了一半。在造錯誤數據時,一定要靜下心來耐心出,不要指望一下就能造出錯誤數據。并且造出的數據盡量不要規模太小、太簡單,數據越復雜,找到錯誤的概率會越高。對于 ACM/ICPC 這種團隊比賽,必要的時候你可以讓隊友幫你造數據。例如某年 ACM/ICPC 沈陽賽區,我當時調試一道模擬題半個多小時還是錯的,距離比賽結束還有?1010?分鐘時,隊友找到一組錯誤數據,然后我調了?55?分鐘就找出了那個致命的 bug。在比賽進行到?295295?分鐘(比賽一共?300300?分鐘)的時候正確通過了這題(很關鍵的一題)。同年上海賽區,也是最后幾分鐘隊友給出一組給力數據,在?299299?分鐘絕殺一道 dp 題目。有了數據以后,剩下的調試應該很簡單了。根據錯誤數據,輸出一些重要的中間變量的值,然后觀察是否和預期一樣。這里也可以借助二分思想輸出中間變量,快速定位到錯誤代碼塊。不過實踐中,通常是根據經驗,覺得哪塊容易出錯就重點輸出哪一塊的變量。無論是平時訓練還是比賽中我都建議少用 IDE 斷點調試功能和單步調試功能,通常比較浪費時間。
3) 對于某些特殊題目,小數據可以很容易寫出一個時間效率低但確保正確的“暴力”程序。這時候,我們可以用暴力程序和出錯程序對拍。對于造出的一些小數據,同時用自己的程序和暴力程序得出答案然后對比。這個小數據的范圍是暴力程序能在短時間內得到正確結果的最大范圍。因為暴力程序一般都很簡單,沒那么容易寫錯,所以你通常把暴力程序當成小數據的標準程序。如果連暴力都寫錯,建議多做練習,提高代碼能力,確保短代碼都能盡量做到零失誤。
經過上面的 debug 過程以后,如果你手造的復雜數據多達?1010?組以上還沒有發現錯誤。你可以嘗試下面的做法:
-
重新思考一個新思路,或者嘗試去發現原思路中的問題。
-
先靜下心來,先看看其他題,AC 一些其他題調整一下狀態,然后回來重新 debug。
-
如果你確定思路一定沒問題,代碼也一定沒錯,那通常是因為你讀錯題目了,重新回去讀題。
-
如果到這一步你的程序還是無法正確通過,并且你確保沒讀錯題,只要有足夠的自信,聯系出題人,和他確認數據是否有問題。
debug 過程中不要輕易請教別人,請教別人思路沒問題,但是請教別人幫你 debug 不太好。除非你已經連續 debug 了一天還沒有發現錯誤,再考慮去請教其他人。這里教大家一個 debug 技巧——斷言(assert)。
assert(x >= 0);
如果x >= 0
不成立,則程序會因為運行錯誤而退出。比如寫一個整數除法函數:
int division(int x, int y) {
assert(y != 0);
return x / y;
}
如果y == 0
程序就會異常退出,你一定是在程序的其他什么地方寫錯了。
再如,solve(x, y)
如果應該等于solve(y, x)
,我們可以assert(solve(x, y) == solve(y, x))
,如果運行錯誤,那么必然solve
寫錯了。在解決 Polya 計數題目的時候,可以assert(sum % |G| == 0)
。除了用來調試以外,assert
還可以用來驗證數據是否規范,對出題人很方便,還可以用來驗證 OJ 數據的準確性。
經驗之談
最后列出一些比賽和訓練中特別容易犯的一些低級錯誤和治療方法:
-
錯誤代碼
1int n;
2int a[n];
治療方法:初學者很容易寫出這樣的代碼,當然老隊員肯定寫不出這種代碼的。盡量避免這種寫法,定義數組用常量,比題目約定的數據范圍稍微大一點。比如數據范圍是?1 \leq n \leq 100001≤n≤10000,則開一個?10000 + 1010000+10?的數組會比較穩妥,因為你也許后來心血來潮讓下標從?11?開始計數。
1const int maxn = 10000 + 10;
2int a[maxn];
-
錯誤代碼
1for (int i = 0; i < n; i++) {
2if (i = n) {
3printf("%d\n", i);
4}
5else {
6printf("%d ", i);
7}
8}
治療方法:剁手。編譯警告(
warning
)會提醒的,不要忽略甚至直接關閉編譯警告。建議在做題的時候把編譯選項-Wall
打開:1g++ -Wall -o main main.cpp
-
錯誤代碼
1double a = 1 / 3 * 3;
2double b = 1;
3if (a == b) {
4printf("Yes");
5}
治療方法:判斷浮點數相等應該用極小值
eps
來輔助,一般eps
取1e-8
足夠了,確保比題目約定的精度誤差要求更小。1const double eps = 1e-8;
2double a = 1 / 3 * 3;
3double b = 1;
4if (fabs(a - b) < eps) {
5printf("Yes");
6}
-
錯誤代碼
1const int inf = 0x7fffffff;
2int dp[10];
3int main() {
4for (int i = 0; i < 10; ++i) {
5dp[i] = inf;
6}
7for (int i = 1; i < 10; ++i) {
8dp[i] = min(dp[i], dp[i] + dp[i - 1]);
9}
10return 0;
11}
治療方法:這里
inf + inf
會溢出,超出了int
的范圍。可以把inf
的定義改成:const int inf = 0x3fffffff
,就可以確保不會溢出了。順便給大家推薦一個小技巧:1int dist[100];
2memset(dist, 0x3f, sizeof(dist));
如上的代碼可以讓
dist
數組中的所有元素賦值為0x3f3f3f3f
,并且兩個初始值相加也不會溢出,常用于圖論或動態規劃中數組的初始化。 -
錯誤代碼
1// 1. 線段樹 build
2void build(int id, int l, int r) {
3if (l == r) {
4return;
5}
6int mid = (l + r) >> 1;
7build(id << 1, l, mid);
8build(id << 1 + 1, mid + 1, r);
9}
10// 2. 取 dp 答案
11printf("%d\n", dp[1 << n - 1]);
治療方法:沒有弄清楚操作符優先級。在優先級不確定的情況下,用小括號來明確指定優先級能夠避免這類問題的發生。當然,最好還是要弄清這些符號之間優先級的關系。
1// 1. 線段樹 build
2void build(int id, int l, int r) {
3if (l == r) {
4return;
5}
6int mid = (l + r) >> 1;
7build(id << 1, l, mid);
8build(id << 1 | 1, mid + 1, r);
9}
10// 2. 取 dp 答案
11printf("%d\n", dp[(1 << n) - 1]);
-
錯誤代碼
123int a[MAXN * 4];
4int main() {
5int x = MULTIPLY(1 + 2, 3);
6return 0;
7}
治療方法:盡量不要用宏定義,常量用
const
來定義。宏定義雖然很方便,但是用起來很容易出錯,比如上面這段代碼。如果你一定要用宏定義,先去了解宏定義常見的“坑”再用。 -
錯誤代碼
12using namespace std;
3bool t, first;
4?
5int main() {
6first = true;
7cin >> t;
8while (t--) {
9...
10}
11return 0;
12}
治療方法:寫代碼的時候不要太“隨性”,這種情況的發生通常都是寫程序時中途加了一個變量導致的,只要不太粗心就能避免。由于可能會發生這類錯誤,所以在本地造數據的時候,對于多組數據的題目要盡可能在一次測試中多造幾組數據,以盡量避免此類問題。
-
在大多數平臺和 ACM/ICPC 現場賽時,C++ 的 long long 用
%lld
輸入;而對于一些搭建在 Windows 上的 OJ(如 Codeforces、HDOJ),要用%I64d
讀入。具體使用哪個占位符要多看?FAQ。 -
變量在訪問前一定要初始化。好的習慣是在定義一個變量的時候就立刻初始化。一定注意,很多平臺(包括計蒜客的題庫)的編譯器是不會在定義數組后將數組內元素全部初始化為?00?的,如果你遇到本地和線上結果不一致的情況,可以從這個方向來找問題。
-
避免訪問非法內存。訪問非法內存的事情經常發生,但是可以通過養成好習慣來避免。比如
stack
、queue
、set
訪問之前必須先確認不為空;訪問指針之前確保指針不是野指針;數組內存開得足夠大,等等。