暴力枚举法总结

集训快要结束了,按照要求需要写一篇关于枚举的总结,于是在网上也看了许多其他菊苣写的文章,深受启发,但是思来想去感觉又不太系统,于是希望能在吸收那些知识后做一些整理,帮助后面的新人。

 

枚举的基本方法:

  枚举,枚举,顾名思义,就是将所有情况都举出,并判断其是否符合题目条件。所以枚举的基本方法便是分析题意后,找到一个合适的维度列举每一个元素,以完成题目。其中如何找到一个合适的维度来进行枚举便是其中的最大难点。

 

枚举的基本条件:

  首先是时间条件。一般来说主流的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;
}

提示:本题采用枚举是因为数据范围的独特性,当数据范围较小的时候,使用枚举的办法是一种好的办法。

 

 

posted @ 2017-07-31 14:52  87hbteo  阅读(4781)  评论(0编辑  收藏  举报