基础算法:枚举

枚举算法介绍

  • 枚举算法是我们在日常中使用到的最多的一个算法,它的核心思想就是:枚举所有的可能。
  • 枚举法的本质就是从所有候选答案中去搜索正确的解,使用该算法需要满足两个条件:
    (1)可预先确定候选答案的数量;
    (2)候选答案的范围在求解之前必须有一个确定的集合。
  • 枚举算法简单粗暴,他暴力的枚举所有可能,尽可能地尝试所有的方法。虽然枚举算法非常暴力,而且速度可能很慢,但确实我们最应该优先考虑的!因为枚举法变成实现最简单,并且得到的结果总是正确的。
  • 枚举算法分为循环枚举、子集枚举、排列枚举三种。

部分枚举算法题解

统计方形(洛谷P2241)

有一个 n✖m方格的棋盘,求其方格包含多少正方形、长方形(不包含正方形)。

分析

矩形包含长方形和正方形,因此可以算出棋盘有多少个矩形,再找出有多少个正方形,两个相减就可以算出长方形的个数。这里枚举的方法比较多,可以枚举点,也可以枚举边。
我做的时候是枚举边。横向所有可能的边长为((n + 1) * n) / 2,纵向所有可能的边长为((m + 1) * m) / 2,则棋盘包含的所有的矩形个数为两式相乘。再计算正方形的个数,正方形最大边长一定是m和n中的较小的那一个,于是再循环枚举所有可能的正方形边长,即1、2、3、…、min(m,n),再相减就能得到长方形的个数。

AC代码
#include
#include
using namespace std;
int main()
{
    long long n, m;
    long long seq = 0;  //正方形个数
    long long rec, sum;  //长方形个数、矩形总个数
    scanf("%lld", &n);
    scanf("%lld", &m);
    long long x = n;
    long long y = m;
    sum = (((n + 1) * n) / 2) * (((m + 1) * m) / 2);
    for(int i = 1; i <= min(m, n); i++)
    {
        seq += x * y;
        x--;
        y--;
    }
    rec = sum - seq;
    printf("%lld %lld", seq, rec);
    return 0;
}

涂国旗(洛谷P3392)

某国法律规定,只要一个由 N✖M 个小方块组成的旗帜符合如下规则,就是合法的国旗。

  • 从最上方若干行(至少一行)的格子全部是白色的;
  • 接下来若干行(至少一行)的格子全部是蓝色的;
  • 剩下的行(至少一行)全部是红色的;

现有一个棋盘状的布,分成了 N 行 M 列的格子,每个格子是白色蓝色红色之一,小 a 希望把这个布改成该国国旗,方法是在一些格子上涂颜料,盖住之前的颜色。

小a很懒,希望涂最少的格子,使这块布成为一个合法的国旗。

分析

首先,三种颜色都要占一行,然后从边界开始枚举每一种情况所需要操作的次数,记录最小值

AC代码
#include
#include
#include
#define N 55
using namespace std;
char flag[N][N];
int min_num = 10000;
int main()
{
    int n, m;
    int step;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
            cin >> flag[i][j];
    }
    for(int i = 1; i <= n - 2; i++)  //白与蓝的边界
    {
        for(int j = i + 1; j <= n - 1; j++)  //蓝与红的边界
        {
            step = 0;
            //开始枚举
            for(int k = 1; k <= i; k++)
                for(int l = 1; l <= m; l++)
                {
                    if(flag[k][l] != 'W')
                        step++;
                }
            for(int k = i + 1; k <= j; k++)
                for(int l = 1; l <= m; l++)
                {
                    if(flag[k][l] != 'B')
                        step++;
                }
            for(int k = j + 1; k <= n; k++)
                for(int l = 1; l <= m; l++)
                {
                    if(flag[k][l] != 'R')
                        step++;
                }
            min_num = min(min_num, step);
        }
    }
    cout << min_num;
    return 0;
}

First Step(P3654)

呐呐呐,这道题太过于二次元我就不写了吧(逃

分析

这题思路其实比较容易想到。遍历一个二维数组,然后遇到 “.” 的就看右方和下方(也可以左方和上方)相距K格内是否有障碍物(即 “#” ),如果没有,则站位方式++,枚举结束输出。
此外需要注意的是,当队伍只有一个人(即K=1)的时候,由于右方和下方是相同的情况,所以等于同一个点枚举了两次,此时最终结果要除以2

AC代码
#include
#define N 105
using namespace std;
char a[N][N];
int main()
{
    int r, c, k;
    int sum = 0;
    cin >> r >> c >> k;
    for(int i = 1; i <= r; i++)
        for(int j = 1; j <= c; j++)
            cin >> a[i][j];
    for(int i = 1; i <= r; i++)
    {
        for(int j = 1; j <= c; j++)
        {
            if(a[i][j] == '.')
            {   int tmp_1 = 0;
                int tmp_2 = 0;
                //枚举点的右边
                for(int m = 1; m < k; m++)
                {
                    if(a[i][j + m] != '.')
                    {
                        tmp_1 = 1;
                        break;
                    }
                    if(j + m > c)  //越界处理
                    {
                        tmp_1 = 1;
                        break;
                    }
                }
                if(tmp_1 == 0)
                    sum++;

                //枚举点的下边
                for(int m = 1; m < k; m++)
                {
                    if(a[i + m][j] != '.')
                    {
                        tmp_2 = 1;
                        break;
                    }
                    if(i + m > r)  //越界处理
                    {
                        tmp_2 = 1;
                        break;
                    }
                }
                if(tmp_2 == 0)
                    sum++;
            }
        }
    }
    //特判
    if(k == 1)
        sum /= 2;
    cout << sum;
    return 0;
}

[USACO1.5]回文质数 Prime Palindromes(洛谷P1217)

为 151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 是回文质数。

写一个程序来找出范围 [a,b] (5 <= a < b <= 100,000,000)间的所有回文质数。

分析

虽然此题是枚举所有的数,但个人认为严格意义上这并不算一道枚举题,因为问题核心就是回文数判断和质数判断两部分,所以整个程序就分为三块:判断回文数、判断质数、枚举。
1、回文数的判断:将数字反过来,如果相等则为回文数

int Judge_Palindromes(long long x)
{
    long long y = x, num = 0;
    while(y != 0)
    {
        num = num * 10 + y % 10;//上一次数字的记录进位再加上下一位数
        y /= 10;
    }
    if(num == x)
        return 1;
    else
        return 0;
}

2、质数判断:开根号以减少运算时间

int Judge_Prime(long long x)
{
    long long tmp = sqrt(x);  //减小计算量
    for(int i = 2; i <= tmp; i++)
    {
        if(x % i == 0)  //如果有一个可以整除,则不是质数
            return 0;
    }
    return 1;
}

因为最后TLE而增加特例来过题目的我是屑(逃

AC代码
#include
#include
using namespace std;
long long a, b;
//回文数判断
int Judge_Palindromes(long long x)
{
    long long y = x, num = 0;
    while(y != 0)
    {
        num = num * 10 + y % 10;//上一次数字的记录进位再加上下一位数
        y /= 10;
    }
    if(num == x)
        return 1;
    else
        return 0;
}
//质数判断
int Judge_Prime(long long x)
{
    long long tmp = sqrt(x);  //减小计算量
    for(int i = 2; i <= tmp; i++)
    {
        if(x % i == 0)  //如果有一个可以整除,则不是质数
            return 0;
    }
    return 1;
}
int main()
{
    cin >> a >> b;
    for(long long i = a; i <= b; i++)
    {
        if(i==9989900) //如果到了这个数,就break 
			break;
        if(Judge_Palindromes(i) == 1)
        {
            if(Judge_Prime(i) == 1)
                cout << i << endl;
        }
    }
    return 0;
}

你可能感兴趣的:(算法与数据结构,算法,数据结构,枚举类)