问答详情

个人中心

程序设计竞赛基本算法—枚举

2015-11-21 浏览 2367 关注 0
枚举

【引言】

小的时候,我们刚开始学习数学的时候,就会先学习里面的数字,老师会教我们1-10的读法以及写法,对于第一个数字1,它就是集合[1,10]中的一个枚举。

【枚举法定义】

在进行归纳推理时,如果逐个考察某类事件的所有可能情况,因而得出一般结论,那么这结论是可靠的,这种归纳方法叫做枚举法。

在枚举过程中将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,合适就保留,不合适就丢弃。所以说枚举得到的结果肯定是正确的。但上面也说道了,枚举法对得到的结果逐一进行判断,这样的话时间复杂度是非常高的。而且判断标准对思维的细腻程度的要求其实很高,要做到无遗漏。

许多人对枚举法的第一印象就是“暴力法”“for循环法”,个人感觉枚举是一种思考问题的方式,是一种思想。枚举并不是单纯的利用“for循环”,而是在任何情况下,选准最合适的对象处理对象,这是关键,而选择处理对象是在优化算法,减少求解步骤,缩小解空间前提下选择的。

例题1:HDU 2012--素数判定

题目大意:对于表达式n^2+n+41,当n在(x,y)范围内取整数值时(包括x,y)(-39<=x

枚举 x--y的所有值,看哪些满足表达式

int main(){
    int x,y,i,j,m,z;
    while((scanf("%d%d", &x, &y), x || y)){
        z=0;
       for(i = x; i <= y; i++){
           m = i * i + i + 41;
           for(j = 2; j <= sqrt(m); j++){
               if(m % j == 0)
                 z++;
           }
       }
       if(z == 0)
           printf("OK\n");
       else
           printf("Sorry\n");
    }
    return 0;
}

例题2:HDU1172 猜数字

题意:计算机随机产生一个四位数,然后玩家去猜这个四位数,每次猜测,计算机都会告诉你猜对了几个数字,有几个数字在正确的位置上。现在给你N组猜测的结果,那么问题来了:能否根据这几组猜测的结果去确定这个四位数是多少。如果能确定,输出这个四位数。如果不能确定,输出:"Not sure"。

思路:

只是四位数的话,不要想着如何通过已有的条件来得到正确的值,而是枚举0000-9999这10000个数就可以了。找到满足这几组猜测结果的四位数。如果只有1个,就输出这个数。如果是0个或是多于1个,就输出:"Not sure"。

AC代码:

#define maxn 100
using namespace std;

struct Node{
    int a,b,c;
};

Node node [maxn];
bool check(int k,int num){
    int num1[5], num2[5];
    bool vis[5];
    for(int i = 1; i <= 4; ++i)
        vis[i] = 0;
    num1[1] = node[k].a / 1000;
    num1[2] = node[k].a % 1000 / 100;
    num1[3] = node[k].a % 100 / 10;
    num1[4] = node[k].a % 10;

    num2[1] = num / 1000;
    num2[2] = num % 1000 / 100;
    num2[3] = num % 100 / 10;
    num2[4] = num % 10;

    int ans = 0;
    for(int i = 1; i <= 4; ++i)//相同位置相同的数
        if(num1[i] == num2[i])
            ans++;
    if(ans != node[k].c)
        return false;//如果个数不符就返回错

    ans = 0;
    for(int i = 1; i <= 4; ++i){//相同数字的个数
        for(int j = 1; j <= 4; ++j){
            if(num1[i] == num2[j] && !vis[j]){
                vis[j] = true;
                ans++; break;
            }
        }
    }
    if(ans != node[k].b)
        return false;
    return true;
}

int main(){
    int n;
    while(scanf("%d", &n), n){
        for(int i = 1; i <= n; ++i)
            scanf("%d%d%d", &node[i].a, &node[i].b, &node[i].c);
        int ans=0, result;
        bool flag = true;
        for(int j = 1000; j <= 9999; ++j){//暴力枚举
            for(int i = 1; i <= n; ++i){
                flag = check(i, j);
                if(!flag)
                    break;
            }
            if(flag){
                ans++;
                result = j;
            }
        }
        if(ans == 1){
           printf("%d\n",result);
        }
        else
            printf("Not sure\n");
    }
    return 0;
}

【枚举和其他算法相结合】

HDU 1598--find the most comfortable road【并查集 + 枚举】

题目大意:城市之间通过许多条高速公路相连,每条高速公路有个速度限制,人们在高速公路上行驶时舒适度有要求,在行驶过程中最高速度与最低速度的差越小乘坐越舒服,问最佳路线的舒适度最高速与最低速的差。

思路:本题是并查集和枚举法的结合,我们可以对每条路的速度从大到小进行排序,从速度最小的边开始枚举,把比它速度大的边加入集合,直到起点跟终点属于同一个集合为止,然后看看此时最大边-最小边的差。记录下来,再从速度第二小的边开始枚举,把比它速度大的边加入集合,直到起点跟终点属于同一个集合为止,然后看看此时最大边-最小边的差。记录下来。一直枚举到最后一条边。比较每次枚举时最大边和最小边的差,就可以得到我们要的结果。

AC代码如下:

#define inf 0x3f3f3f3f
using namespace std;

struct node
{
    int a,b,c;
};
node map[1100];

int cmp(node s1,node s2){
    return s1.c < s2.c;
}

int per[220];
int n,m;
int find(int x){
    if(per[x] == -1)
        return x;
    return per[x] = find(per[x]);  
}


int main (){
    while(scanf("%d %d", &n, &m)!=EOF){
        for(int i = 1; i <= m; ++i)
            scanf("%d %d %d", &map[i].a, &map[i].b, &map[i].c);
        sort(map + 1map + 1 + m, cmp); 
        int Q;
        scanf("%d", &Q);
        int st,ed;      
        while(Q--){
          int MIN = inf;
            scanf("%d %d",&st, &ed);
//枚举最小边,再从小到大一直加大于等于这个最小边的其他边,直到起点跟终点属于同一个集合为止,然后用最大边-最小边的差
            for(int i = 1; i <= m; ++i){ 
                memset(per, -1sizeof(per));
                for(int j = i; j <= m; ++j){
                    int fa = find(map[j].a);
                    int fb = find(map[j].b);
                    if(fa != fb)
                        per[fb] = fa;
                    if(find(st) == find(ed)){
                        MIN = min(MIN, map[j].c - map[i].c);
                        break;
                    }
                }
            }
           if(MIN == inf)
                printf("-1\n");
            else 
                printf("%d\n", MIN);
        }
    }
    return 0;
}


POJ 3080 Blue Jeans 【KMP &&暴力枚举】

题意:给你N个串,让你找出最长公共子串(长度必须大于2)。若有长度相同的子串,输出字典序较小的。

思路:后缀数组可以解决这个问题,但是没有必要用后缀数组,我们可以枚举一个串的所有子串,判断该子串有没有在其它串中出现。若在其它串中全部出现 则更新子串,否则枚举下一个子串。

using namespace std;
char str[15][100];
int next[100];

void getnext(char *s1){
    int j = 0, k = -1;
    int len = strlen(s1);
    next[0] = -1;
    while(j < len){
        if(k == -1 || s1[j] == s1[k]){
            ++j;
            ++k;
            next[j] = k;
        }
        else k = next[k];
    }
    return ;
}
bool kmp(char *s1, char *s2){
    int len1 = strlen(s2);
    int len2 = strlen(s1);
    getnext(s1);
    int i = 0, j = 0;
    while(i < len1){
        if(j == -1 || s1[j] == s2[i]){
            ++i;
            ++j;
        }
        else j=next[j];
        if(j == len2)
            return true;
    }
    return false;
}
int main(){
    int T;
    scanf("%d", &T);
    char temp[100];
    char sum[100];
    while(T--){
        int n;
        scanf("%d", &n);
        for(int i = 0; i < n; ++i)
            scanf("%s", str[i]);
        int len = strlen(str[0]);
        memset(sum, '\0'sizeof(sum));
        for(int i = 0; i < len; ++i){
            int ans = 0;
            for(int j = i; j < len; ++j){
          //枚举一一个串的所有子串,存储在新串中
                temp[ans++] = str[0][j];
                temp[ans] = '\0';
                int flag = 1;
                for(int k = 1; k < n; ++k){//新的串和str[]匹配
                    if(!kmp(temp, str[k])){//匹配失败
                        flag = 0;
                        break;
                    }
                }
                if(flag){//匹配成功
                    //最长公共子串而且字典序最小
                    if(strlen(temp) > strlen(sum))
                        strcpy(sum, temp);
                    else if(strlen(temp)==strlen(sum) && strcmp(temp, sum) < 0)
                        strcpy(sum, temp);
                }
            }
        }
        if(strlen(sum) < 3)
            printf("no significant commonalities\n");
        else
            printf("%s\n", sum);
    }
    return 0;
}

 本文由金磊提供,适用于ACM入门者,未经允许,禁止转载。                       

问答发起人 万子德 万子德 中国科学院大学

全部回答(0)

还木有回答哦,快抢占第一把交椅吧!

- 查看更多和回答问题请下载赛氪APP -
赛乐云AI 证书查询
取消 确认

同学~下载赛氪APP就可以进群咯~
先不聊 去下载