暴力枚举法总结
集训快要结束了,按照要求需要写一篇关于枚举的总结,于是在网上也看了许多其他菊苣写的文章,深受启发,但是思来想去感觉又不太系统,于是希望能在吸收那些知识后做一些整理,帮助后面的新人。
枚举的基本方法:
枚举,枚举,顾名思义,就是将所有情况都举出,并判断其是否符合题目条件。所以枚举的基本方法便是分析题意后,找到一个合适的维度列举每一个元素,以完成题目。其中如何找到一个合适的维度来进行枚举便是其中的最大难点。
枚举的基本条件:
首先是时间条件。一般来说主流的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; }
提示:本题采用枚举是因为数据范围的独特性,当数据范围较小的时候,使用枚举的办法是一种好的办法。