暴力求解法之简单枚举

简单枚举


枚举是暴力求解法最基本最简单的一种方法,许多问题通过枚举就能找到解。当然,暴力求解法也不是无脑求解问题,通过对问题的分析减少枚举的规模可以使得算法更加的简洁和高效。


例1. 输入正整数n,按从小到大的顺序输出所有形如abcde/fghij=n的表达式,其中a-j恰好为数字0-9的一个排列(可以有前导0), 2 ≤ n ≤ 79 2\le n\le 79 2n79

分析问题可知,abcde和fghij正好是0-9的排列,由排列公式 A 10 10 = 10 ! = 3628800 A_{10}^{10}=10!=3628800 A1010=10!=3628800知排列的结果共有3628800种。显然,直接枚举0-9的排列不是一种高效的方式。如果枚举fghij呢,由排列公式 A 10 5 = 10 ! 5 ! = 30240 A_{10}^{5}=\frac{10!}{5!}=30240 A105=5!10!=30240,考虑到n≥2,则fghij中的f≤5,于是枚举的次数应小于 C 6 1 A 9 4 = 18144 C_6^1A_9^4=18144 C61A94=18144。这里n=2是最坏的情况,如果n越大,则枚举的次数越少。但是,枚举fghij的所有可能排列涉及到的排列算法会消耗大量时间,下面通过枚举fghij所有排列的方法和直接枚举fghij的取值为1000-98765/n的方法进行比较。
(1)fghij直接枚举1000-98765/k所有值

#include 
#include 
#include 
#include 
using namespace std;
int k=7;
int check(int a){
    int flag[10]={0};
    int b = a*k;
    for(int i=0; i<5; i++){
        flag[a%10]=1; flag[b%10]=1;
        a/=10;b/=10;
    }
    for(int i=0; i<10; i++)
        if(flag[i]!=1) return 0;
    return 1;
}
int main(){
        int startTime=clock();
         for(int i=1000; i<= 98765/k; i++){
            if(check(i)) cout << i << " " << i*k << endl;
        }
        int finishTime=clock();
        cout << "----------------> n=" << k << " " << (double)(finishTime-startTime)/CLOCKS_PER_SEC << "S" << endl;
    return 0;
}
2394 16758
2637 18459
4527 31689
5274 36918
5418 37926
5976 41832
7614 53298
14076 98532
----------------> n=7 0.005S

(2)fghij枚举1000-98765/k的可能排列

#include 
#include 
#include 
#include 
using namespace std;
int k=7;
int check(int a){
    int flag[10]={0};
    int b = a*k;
    for(int i=0; i<5; i++){
        flag[a%10]=1; flag[b%10]=1;
        a/=10;b/=10;
    }
    for(int i=0; i<10; i++)
        if(flag[i]!=1) return 0;
    return 1;
}
int dat=0;
void my_permutation(int n, vector<int> vec){
    if(n == vec.size()){ // 如果vec中已经包含1, 2, ...n的所有元素,则为递归中止条件
        dat=vec[0]*10000+vec[1]*1000+vec[2]*100+vec[3]*10+vec[4];
        if(dat<(98765/k) && check(dat)) cout << dat << " " << dat*k << endl;
    }else{
        for(int i=0; i<10; i++){
            if(find(vec.begin(), vec.end(), i) == vec.end()){
                vec.push_back(i);
                my_permutation(n, vec);
                vec.pop_back(); // 恢复
            }
        }
    }
}
int main(){
    vector<int> vec;
    int startTime=clock();
    my_permutation(5, vec);
    int finishTime=clock();
    cout << "----------------> n=" << k << " " << (double)(finishTime-startTime)/CLOCKS_PER_SEC << "S" << endl;
    return 0;
}
2394 16758
2637 18459
4527 31689
5274 36918
5418 37926
5976 41832
7614 53298
14076 98532
----------------> n=7 0.072S

从结果可以看出,当n取值为7时,一共有8组结果满足要求。时间上,第一种方法用时约0.005s,而第二种方法用时达0.072s,显然第一种方法的耗时最短,实际上,当n取题目中要求的2到79的范围内,第一种方法都比第二种方法更高效。


例2. 输入n个元素组成的序列S, 找出一个乘积最大的连续子序列。如果这个最大的乘积不是正数,应输出0(表示无解)。 1 ≤ n ≤ 18 , − 10 ≤ S i ≤ 10 1≤n≤18, -10≤S_i≤10 1n1810Si10.

直接枚举所有的起点和终点位置:

#include 
using namespace std;
int main(){
    int n;
    int elem[20]={0};
    long long maxProd=1, maxTemp=1;
    freopen("data.txt", "r", stdin);
    while(cin >> n){
        for(int i=0; i<n; i++){
           cin >> elem[i];
        }
        maxProd= 0;
        for(int start = 0; start < n; start++)
            for(int end = start; end < n; end++){
                maxTemp = 1;
                for(int i = start; i <= end ; i++) maxTemp*=elem[i];
                if(maxTemp > maxProd) maxProd = maxTemp;
            }
        if(maxProd > 0) cout << maxProd << endl;
        else cout << 0 << endl;
        maxProd = 1;
    }
    fclose(stdin);
    return 1;
}

你可能感兴趣的:(算法)