暴力枚舉法總結


集訓快要結束了,按照要求需要寫一篇關於枚舉的總結,於是在網上也看了許多其他菊苣寫的文章,深受啟發,但是思來想去感覺又不太系統,於是希望能在吸收那些知識后做一些整理,幫助后面的新人。

 

枚舉的基本方法:

  枚舉,枚舉,顧名思義,就是將所有情況都舉出,並判斷其是否符合題目條件。所以枚舉的基本方法便是分析題意后,找到一個合適的維度列舉每一個元素,以完成題目。其中如何找到一個合適的維度來進行枚舉便是其中的最大難點。

 

枚舉的基本條件:

  首先是時間條件。一般來說主流的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。

為了加快速度,可以將0到2222的所有整數需要的火柴棒數目提前算好保存在數組中。
 
代碼:
#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)

DESCRIPTION

今天HHHH考完了期末考試,他在教學樓里閑逛,他看着教學樓里一間間的教室,於是開始思考:

如果從一個坐標為 (x1,y1,z1)(x1,y1,z1)的教室走到(x2,y2,z2)(x2,y2,z2)的距離為 |x1x2|+|y1y2|+|z1z2||x1−x2|+|y1−y2|+|z1−z2|

那么有多少對教室之間的距離是不超過RR的呢?

INPUT
第一行是一個整數 T(1T10)T(1≤T≤10), 表示有TT組數據 接下來是TT組數據,對於每組數據: 第一行是兩個整數n,q(1n5×104,1q103)n,q(1≤n≤5×104,1≤q≤103), 表示有nn間教室, qq次詢問. 接下來是nn行, 每行3個整數xi,yi,zi(0xi,yi,zi10)xi,yi,zi(0≤xi,yi,zi≤10),表示這間教室的坐標. 最后是qq行,每行一個整數R(0R109)R(0≤R≤109),意思見描述.
OUTPUT
對於每個詢問 RR輸出一行一個整數,表示有多少對教室滿足題目所述的距離關系.
SAMPLE INPUT
1 3 3 0 0 0 1 1 1 1 1 1 1 2 3
SAMPLE OUTPUT
1 1 3
HINT
對於樣例,1號教室和2號教室之間的距離為3, 1號和3號之間的距離為3, 2號和3號之間的距離為0

 

題意:在一個三維空間中有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;
}

提示:本題采用枚舉是因為數據范圍的獨特性,當數據范圍較小的時候,使用枚舉的辦法是一種好的辦法。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM