引言:
总结一下这个简单的算法~
一、什么是枚举算法:
枚举算法很简单,就是将问题的所有可能列举出来,然后通过筛选,找出解,它的时间复杂度通常很大,在一些题目中可能会TLE,因此重要的是如何优化所写的枚举算法。
二、枚举算法优化的思路:
删除重复和不可能出现的情况。重复和不可能出现的情况都列出来,找到其中的规律。
三、经典例题:
我们上一道经典例题:
题目描述:有一个n×m 方格的棋盘,求其方格包含多少正方形、长方形(不包含正方形)。
输入格式:一行,两个正整数n,m(n≤5000,m≤5000)。
输出格式:一行,两个正整数,分别表示方格包含多少正方形、长方形(不包含正方形)。
样例:输入——2 3 输出——8 10
我的最终代码:
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
unsigned long long n, m;
cin >> n >> m;
unsigned long long row, pointr;
unsigned long long oblong = 0;
unsigned long long square = 0;
oblong = ((1.0/2)*m*m+(1.0/2)*m)*((1.0/2)*n*n+(1.0/2)*n);
for ( pointr = 1; pointr <= n; pointr++)
{
row = 1;
unsigned long long a = pointr - row;
unsigned long long x = m - a;
unsigned long long times = n - a;
square += x*times;
}
cout << square << " " << oblong - square << endl;
system("pause");
return 0;
}
第一次思路:
纯暴力枚举,确定一个矩形需要四个指针,四个指针可以确定长和宽长度,因此分别设为row(列底),pointr(列顶),col(行首),pointc(行尾),我用word文档以展示:
长就是pointc - col + 1,宽就是pointr - row + 1。
我们设a = pointr - row,b = pointc - col,注意,a和b并不是长和宽,而是长宽-1得到的,因此当长或宽为1时,代表的a和b是0.
只要判断他们相等是一个正方形。
在判断之前,我们通过找规律可以总结出一个n*m矩形的所有子矩形的公式:
oblong = ((1.0/2)*m*m+(1.0/2)*m)*((1.0/2)*n*n+(1.0/2)*n);
因此,只要将总子矩形个数减去正方形个数,就是除了正方形之外的长方形个数。
第一次暴力的代码,用了四个for,直接TLE了不少样例,只AC了3个。
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
unsigned long long n, m;
cin >> n >> m;
unsigned long long row, pointr;
unsigned long long oblong = 0;
unsigned long long square = 0;
oblong = ((1.0/2)*m*m+(1.0/2)*m)*((1.0/2)*n*n+(1.0/2)*n);
for ( row = 1; row <= n; row++)
{
for (pointr = row; pointr <= n; pointr++)
{
for (int col = 1; col <= m; col++)
{
for (int pointc = col; pointc <= m; pointc++)
{
int a = pointr - row;
int b = pointc - col;
if (a == b)
{
square++;
}
}
}
}
}
cout << square << " " << oblong - square << endl;
system("pause");
return 0;
}
第二次思路:
通过找规律,col值不变而pointc向右移动时,正方形只能出现一次,也就是一个col值只能有一个正方形,因此在第四个for,if判断找到正方形后,用break从col进入到col+1,代码只是多了一个break:
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
unsigned long long n, m;
cin >> n >> m;
unsigned long long row, pointr;
unsigned long long oblong = 0;
unsigned long long square = 0;
oblong = ((1.0/2)*m*m+(1.0/2)*m)*((1.0/2)*n*n+(1.0/2)*n);
for ( row = 1; row <= n; row++)
{
for (pointr = row; pointr <= n; pointr++)
{
for (int col = 1; col <= m; col++)
{
for (int pointc = col; pointc <= m; pointc++)
{
int a = pointr - row;
int b = pointc - col;
if (a == b)
{
square++;
break;
}
}
}
}
}
cout << square << " " << oblong - square << endl;
system("pause");
return 0;
}
发现多AC了一个,但是还是有很多TLE了。
第三次思路:
通过找规律,我发现对于一个1*m的矩阵,1*1正方形有m个,那么在暴力枚举找这个1*1正方形时,就会重复枚举1*1正方形m次,能不能只需要枚举1次,就可以一次性找到m个1*1正方形?同理,对于,2*m矩阵,2*2正方形则有m-1个,能不能只需要枚举一次,就可以找到m-1个2*2正方形?
通过一一列举这些已经重复的情况,我们发现一个公式,列数m - 正方形长度 + 1 = 正方形个数。进一步化简,正方形个数x = m - a;
那么我们就可以通过列数的值,确定一个n*m长方形(n > m)的正方形个数,n*m长方形的n*n正方形个数x = m - a;a通过双指针pointr与row得到,因此可以将两个for省略,得到两个for的暴力枚举代码:
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
unsigned long long n, m;
cin >> n >> m;
unsigned long long row, pointr;
unsigned long long oblong = 0;
unsigned long long square = 0;
oblong = ((1.0/2)*m*m+(1.0/2)*m)*((1.0/2)*n*n+(1.0/2)*n);
for ( row = 1; row <= n; row++)
{
for ( pointr = row; pointr <= n; pointr++)
{
int a = pointr - row;
int x = m - a;
square += x;
}
}
cout << square << " " << oblong - square << endl;
system("pause");
return 0;
}
这时候就已经满足时间限制了,大大减少了时间复杂度,然而我还不是很满足,能不能做到一个for?
第四次思路:
仿照寻找重复性的优化思路,我发现在行的方向上也存在重复性,即一个7*6矩阵,分成7个1*6矩阵,那么这7个1*6矩阵的正方形个数是一样的,都是6个,同理,分成6个2*6矩阵,则2*2正方形个数也是一样的,是5个。那么,7*6就得到了7个1*6矩阵的总1*1正方形个数,6*5就得到了6个2*6矩阵的2*2正方形总个数,能不能通过一次枚举,枚举出一个1*6矩阵的1*1正方形个数,就可以得到7个1*6矩阵1*1正方形总个数?
我们发现只需要将1*6矩阵正方形个数乘以7就可以得到1*1正方形总个数,同理2*6矩阵。因此存在一个未知数times,times乘以1*6矩阵正方形个数就可以得到所有的1*1正方形个数,通过列出所有的重复情况,我们得到times = 行数n - a的结果。我们进行一个循环,让row一直在1位置,pointr从1到行数n,pointr和row的每个组合就是:1*6,2*6,3*6,4*6,5*6,6*6,7*6矩阵,可以分别找出所有的1*1,2*2,3*3,4*4,5*5,6*6正方形,于是就有了最上面第一个代码段的结果,只需要一个for就能找出所有的正方形。
看到这里你也很不容易,祝你学习快乐~喜欢的话欢迎点赞关注哦~