集訓快要結束了,按照要求需要寫一篇關於枚舉的總結,於是在網上也看了許多其他菊苣寫的文章,深受啟發,但是思來想去感覺又不太系統,於是希望能在吸收那些知識后做一些整理,幫助后面的新人。
枚舉的基本方法:
枚舉,枚舉,顧名思義,就是將所有情況都舉出,並判斷其是否符合題目條件。所以枚舉的基本方法便是分析題意后,找到一個合適的維度列舉每一個元素,以完成題目。其中如何找到一個合適的維度來進行枚舉便是其中的最大難點。
枚舉的基本條件:
首先是時間條件。一般來說主流的OJ當中,1000ms的時間限制下可以運行操作數為10^7以內的運算(通常10^6以內較為保險),所以在采用枚舉方法之前最好看一下數據范圍,確保整個程序的執行操作數不會超過10^6-10^7這個量級,如果超過了就嘗試更換枚舉的維度或者使用其他算法吧。
其次是編程上的實現條件。在編程實現上,一般來說暴力枚舉需要兩個條件,一是枚舉的范圍一般需要連續,如果枚舉范圍是離散的,那么一般很難使用for循環枚舉出所有狀態,也就不能保證解的完整性(不過有些時候數據看似離散,但實際上可以經過處理變得連續)。第二個條件是枚舉內容需要已知,不能在枚舉到某個地方的時候出現未知(不過這個一般都被滿足)。
枚舉的優點:
1.能舉出所有情況,保證解為正確解。
2.能解決許多用其他算法難以解決的問題。
3.便於思考與編程。
例題一:火柴棒等式:
【問題描述】給你n根火柴棍,你可以拼出多少個形如“A+B=C”的等式?等式中的A、B、C是用火柴棍拼出的整數(若該數非零,則最高位不能是0)。用火柴棍拼數字0-9的拼法如圖所示:

注意:
1. 加號與等號各自需要兩根火柴棍
2. 如果A≠B,則A+B=C與B+A=C視為不同的等式(A、B、C≥0)
3. n根火柴棍必須全部用上
【輸入】輸入一個整數n(n≤24)。
【輸出】輸出能拼成的不同等式的數目。
問題簡述:給你n(n<=24)根火柴棒,叫你拼出 “A + B = C”這樣的等式,求方案數。
思路:由於題目中已經給出,最多有24根火柴,而等號和加號各用4根的前提下,A\B\C三個數則總共只有20根火柴,數據范圍較小,可以用枚舉法枚舉A、B。這個時候我們發現,0-9這10個數字所用的火柴數為:6,2,5,5,4,5,6,3,7,6,很明顯數字1用的火柴棒最少只要2根,不妨讓B為1,那么A和C最多可以使用18根火柴,而C>=A,滿足條件的A的最大取值為1111。所以枚舉A和B的范圍是從0~1111。
#include <iostream> using namespace std; int a[2223]={6,2,5,5,4,5,6,3,7,6}; const int b[10]={6,2,5,5,4,5,6,3,7,6}; //計算自然數n所需要的火柴數 int need(int n) { int tmp, num; num=0; if(n==0) return 6; while(n>0) { tmp=n%10; num+=b[tmp]; n/=10; } return num; } int main( ) { int n,A,B,C,D,sum; cin>>n; sum=0; for(int i=10; i<2223; i++) //預處理 a[i]=need(i); for(int i=0; i<=1000; i++) { for(int j=0; j<=1000; j++) { A=a[i]; B=a[j]; C=n-4-A-B; D=a[i+j]; if(D==C) sum++; } } cout<<sum<<endl; return 0; }
提示:本題使用枚舉的優勢在於數據范圍較小,而且沒有合適的其他算法來處理。
例題二:計算幾何你瞎暴力(玲瓏OJ1143)
今天HHHH考完了期末考試,他在教學樓里閑逛,他看着教學樓里一間間的教室,於是開始思考:
如果從一個坐標為 (x1,y1,z1)(x1,y1,z1)的教室走到(x2,y2,z2)(x2,y2,z2)的距離為 |x1−x2|+|y1−y2|+|z1−z2||x1−x2|+|y1−y2|+|z1−z2|
那么有多少對教室之間的距離是不超過RR的呢?
題意:在一個三維空間中有N個點,q次查詢,每次查詢給一距離r,求出三維空間中有多少對點之間的哈密頓距離小於r。
思路:一開始的時候如果按照朴素的想法,先離線處理,兩兩配對求出每兩個點之間的距離,之后輸出,但是本題中點的數目n的數據較大,如果要全部處理的話需要109左右的操作數,肯定會超時。那么這個時候我們仔細觀察后發現,每一個點的范圍很小,0<=x,y,z<=10,如果我們通過坐標來遍歷每一個點,那么就只需要10^3的復雜度,顯然更合適。所以本題也是如此,通過以坐標為單位的枚舉,就可以得到最后的結果:
代碼:
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int MAX = 10005; const int MOD = 1e9+7; const int INF = 0x3f3f3f3f; int n, q, t, tem; int a, b, c, x, y, z; LL aa[35]; LL dex[15][15][15]; int dis(int aa, int bb, int cc, int xx, int yy, int zz) { return abs(aa-xx)+abs(bb-yy)+abs(cc-zz); } int main() { scanf("%d",&t); while(t--) { memset(aa, 0, sizeof(aa)); memset(dex, 0, sizeof(dex)); scanf("%d%d",&n,&q); while(n--) { scanf("%d%d%d",&x,&y,&z); ++dex[x][y][z]; } for(a = 0; a <= 10; ++a) for(b = 0; b <= 10; ++b) for(c = 0; c <= 10; ++c) if(dex[a][b][c]) for(x = 0; x <= 10; ++x) for(y = 0; y <= 10; ++y) for(z = 0; z <= 10; ++z) if(dex[x][y][z]) { tem = dis(a, b, c, x, y, z); if(tem == 0) aa[tem] += (dex[x][y][z])*(dex[x][y][z]-1)/2; else aa[tem] += dex[x][y][z]*dex[a][b][c]; } for(int i = 1; i <= 30; ++i) aa[i] /= 2; for(int i = 1; i <= 30; ++i) aa[i] += aa[i-1]; while(q--) { scanf("%d",&tem); if(tem > 30) tem = 30; printf("%lld\n",aa[tem]); } } return 0; }
提示:本題采用枚舉是因為數據范圍的獨特性,當數據范圍較小的時候,使用枚舉的辦法是一種好的辦法。
