暴力枚举概念和洛谷例题

一.暴力枚举相关概念

1.基本概念

暴力枚举(也称为穷举法)是一种简单直接的算法设计策略,在 C 语言编程中经常会用到。暴力枚举就是对问题的所有可能情况进行逐一尝试,直到找到满足条件的解或者遍历完所有可能情况。它不依赖于问题的特定结构或巧妙算法,而是通过纯粹的计算能力来解决问题。虽然在某些复杂问题上可能效率不高,但对于一些规模较小的问题或者作为理解问题和初步求解的手段,是非常有效的。

2.适用场景

  • 简单问题求解:当问题的可能解空间相对较小,通过逐一列举所有可能情况能够在可接受的时间内得到答案时,适合使用暴力枚举。例如,找出 1 到 100 之间的所有素数,可以对每个数进行判断是否为素数,这种情况下逐一检查是可行的。
  • 验证其他算法:在设计更高效的算法时,暴力枚举可以作为一种基准来验证新算法的正确性。先通过暴力枚举得到准确结果,再与新算法的结果进行对比,确保新算法的有效性。

3.实现步骤

  • 确定枚举范围

明确问题中需要进行枚举的变量以及它们的取值范围。例如,要找出两个整数在一定范围内(如 1 到 100)满足某种条件的组合,那么这两个整数的取值范围就是 1 到 100,这就是需要枚举的范围。

  • 设计循环结构

根据枚举范围,使用 C 语言中的循环语句(如for循环、while循环等)来遍历所有可能的情况。通常会使用多层循环来处理多个变量的枚举。

  • 进行条件判断

在循环内部,对于每一种枚举出来的情况,需要根据问题的要求进行条件判断,看是否满足特定的条件。如果满足条件,则可以进行相应的处理,比如输出结果、记录满足条件的情况等。

4.注意事项

  • 循环边界条件

在设计循环结构时,一定要准确设置循环的边界条件,确保能够完整地遍历所有需要枚举的情况,同时又不会超出范围导致错误。例如,在上面列举两个整数组合的例子中,如果把循环条件写成for (a = 1; a < 10; a++)for (b = 1; b < 10; b++),就会遗漏一些组合情况。

  • 时间复杂度

暴力枚举的最大缺点就是可能会导致很高的时间复杂度,尤其是当枚举范围很大或者需要枚举的变量较多时。例如,要枚举三个整数在 1 到 100 之间的所有组合,就需要三层for循环,总共会有100×100×100 = 1000000种情况,这会耗费大量的计算时间。所以在实际应用中,要根据问题的规模和要求,考虑是否能够接受暴力枚举带来的时间成本,或者是否需要寻找更高效的算法来替代。

  • 内存使用

虽然暴力枚举本身通常不会占用大量的内存,但在某些情况下,如果需要存储大量枚举出来的结果或者中间数据,也可能会导致内存不足的问题。例如,要枚举所有可能的字符串组合并存储起来,可能就需要考虑内存的使用情况。

总之,暴力枚举是 C 语言中一种基础且常用的算法策略,通过准确确定枚举范围、合理设计循环结构以及正确进行条件判断,可以解决许多简单的数学问题、组合问题等,但在使用时也要注意其时间复杂度和内存使用等方面的问题。

二.洛谷暴力枚举相关习题

1.P2241 统计方形(数据加强版)

P2241 统计方形(数据加强版)

  • 题目描述

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

  • 输入格式

一行,两个正整数 n,m(𝑛≤5000,𝑚≤5000)。

  • 输出格式

一行,两个正整数,分别表示方格包含多少正方形、长方形(不包含正方形)。

如果一开始没什么想法,可以尝试数学方法写一下,思路会很清楚,不要一直专注在代码如何写。

在这里插入图片描述

​ 如图所示:对于一个 2x3 的方阵,1x1 的矩形有2x3 个,1x2 的矩形有2x2 个……,依此类推,可知正方形个数为6 + 2 = 8个,长方形的个数为矩形个数 – 正方形个数 ,即18 - 8 = 10个。

以此类推逐渐扩大n、m。

在这里插入图片描述

​ 如图所示(找规律):一个nxm 的方阵,对于分别所有边长的矩形的计算可以写为(n - i + 1) * (n - j + 1),这里的ij分别代表行和列,即他们的宽和长

​ 对于这种方法其实就是暴力枚举出每一个可能的矩形进行统计,直至判断完全。

#include <stdio.h>
int main() {
	long long int n, m;
	scanf("%lld%lld", &n, &m);
    
	long long int square_count = 0, rectangle_count = 0, all_count = 0;
    
    //判断正方形个数,(n < m) ? n : m是选出较小的作为正方形边长最大值
	for (int i = 1; i <= ((n < m) ? n : m); i++) {
		square_count += (n - i + 1) * (m - i + 1);
	}
    
    //双层循环暴力遍历每一个n、m组成的矩形
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			all_count += (n - i + 1) * (m - j + 1);
		}
	}
    
	rectangle_count = all_count - square_count;
	printf("%lld %lld", square_count, rectangle_count);
    
	return 0;
}

2.P2089 烤鸡

P2089 烤鸡

  • 题目描述

猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 1010 种配料(芥末、孜然等),每种配料可以放 11 到 33 克,任意烤鸡的美味程度为所有配料质量之和。

现在, Hanke 想要知道,如果给你一个美味程度 𝑛n ,请输出这 1010 种配料的所有搭配方案。

  • 输入格式

一个正整数 𝑛n,表示美味程度。

  • 输出格式

第一行,方案总数。

第二行至结束,1010 个数,表示每种配料所放的质量,按字典序排列。

如果没有符合要求的方法,就只要在第一行输出一个 00。

这段代码的功能是根据输入的一个整数 n,通过递归的方式生成所有可能的由 10 种配料组成且每种配料用量在 13 之间的组合情况.

#include <stdio.h>
// 存储每种配料的用量
int a[10];
// 记录符合要求的方案总数
int count = 0;

// 递归函数用于生成所有配料组合

void generate_Combinations_count(int index, int remainingSum, int n) {
    
    //注意数组索引是从 0 到 9,所以 index == 10 表示全部处理完
    if (index == 10) {
        //在处理完所有配料后,如果此时剩余总和为 0,这就说明当前这种配料组合刚好满足总量要求,所以count++
        if (remainingSum == 0) {
            count++;
        }
        return;
    }
    
    for (int i = 1; i <= 3 && i <= remainingSum; i++) {
        a[index] = i;
        generate_Combinations_count(index + 1, remainingSum - i, n);
    }
}

//打印函数
void generate_Combinations_print(int index, int remainingSum, int n) {
    if (index == 10) {
        if (remainingSum == 0) {
            count++;
            for (int i = 0; i < 10; i++) {
                printf("%d ", a[i]);
            }
            printf("\n");
        }
        return;
    }
    
    for (int i = 1; i <= 3 && i <= remainingSum; i++) {
        a[index] = i;
        generate_Combinations_print(index + 1, remainingSum - i, n);
    }
}

int main() {
    int n;
    scanf("%d", &n);

    generate_Combinations_count(0, n, n);

    printf("%d\n", count);

    generate_Combinations_print(0, n, n);

    return 0;
}

递归函数 generate_Combinations_count

  • int index:表示当前正在处理的配料索引。函数会从索引 0 开始,逐步处理到索引 9(因为数组 a 的大小是 10,索引范围是 09

  • int remainingSum:表示在设置当前配料用量之前,剩余的总和。初始时,这个值等于从标准输入读取的 n,随着配料用量的设置,它会不断减少。

  • int n:从 main 函数传入的一个整数,代表某种总量要求,可能是所有配料用量之和的目标值。

本题作者不知道如何用一个函数写完先输出总数再输出方案,所以就把一个函数写了两遍(T–T)

3.P1618 三连击(升级版)

P1618 三连击(升级版)

  • 题目描述

将 1,2,…,91,2,…,9 共 99 个数分成三组,分别组成三个三位数,且使这三个三位数的比例是 𝐴:𝐵:𝐶A:B:C,试求出所有满足条件的三个三位数,若无解,输出 No!!!

  • 输入格式

三个数,𝐴,𝐵,𝐶A,B,C

  • 输出格式

若干行,每行 33 个数字。按照每行第一个数字升序排列。

#include <stdio.h>
//检查是否有九个数
int all_Different(int num1, int num2, int num3) {
    int book[10] = { 0 };
    
    while (num1 > 0) {
        int temp = num1 % 10;
        book[temp]++;
        num1 /= 10;
    }
    while (num2 > 0) {
        int temp = num2 % 10;
        book[temp]++;
        num2 /= 10;
    }
    while (num3 > 0) {
        int temp = num3 % 10;
        book[temp]++;
        num3 /= 10;
    }
    
    for (int i = 1; i <= 9; i++) {
        if (book[i] != 1) {
            return 0;
        }
    }
    return 1;
}

// 检查是否满足比例关系
int check_Connextion(int num1, int num2, int num3, int a, int b, int c) {
    return (num1 * b == num2 * a) && (num1 * c == num3 * a);
}

// 生成所有由1到9组成的三位数组合
void generate_Search(int a, int b, int c) {
    int found = 0;

    for (int i = 123; i <= 987; i++) {
        for (int j = i + 1; j <= 987; j++) {
            for (int k = j + 1; k <= 987; k++) {
                if (check_Connextion(i, j, k, a, b, c) && all_Different(i, j, k)){
                    printf("%d %d %d\n", i, j, k);
                    found = 1;
                }
            }
        }
    }
    if (!found) {
        printf("No!!!\n");
    }
}

int main() {
    int A, B, C;
    scanf("%d%d%d", &A, &B, &C);

    generate_Search(A, B, C);

    return 0;
}

(1.)函数 all_Different

  • 函数功能和目的
    • 该函数的主要目的是检查由三个整数 num1num2num3 所组成的所有数字(即每个整数的每一位数字)是否恰好是 19 这九个不同的数字。
  • 函数内部逻辑
    • 首先定义了一个整型数组 book,大小为 10,并初始化为 0。这个数组用于记录数字 19 出现的次数,数组下标对应数字的值,例如 book[3] 用于记录数字 3 出现的次数。
    • 然后通过三个while循环分别处理num1-num2-num3.
      • 对于每个整数,在循环中通过取余操作 num1 % 10(以 num1 为例)获取该整数的个位数字 temp,然后将 book[temp] 的值增加 1,表示该数字出现了一次。接着通过除法操作 num1 /= 10 将整数缩小 10 倍,以便处理下一位数字,如此循环直到该整数变为 0
    • 最后通过一个for循环遍历数组book的下标从19
      • 如果发现 book 数组中某个元素的值不等于 1,这就意味着 19 这九个数字中存在某个数字出现的次数不是恰好一次,那么就返回 0,表示不满足所有数字都不同的条件。
      • 如果循环结束后没有发现这样的情况,就返回 1,表示三个整数所组成的所有数字恰好是 19 这九个不同的数字。

(2.)函数 check_Connextion

  • 函数功能和目的
    • 该函数用于检查三个整数 num1num2num3 是否满足特定的比例关系。
  • 函数内部逻辑
    • 函数通过判断两个等式 num1 * b == num2 * anum1 * c == num3 * a 是否同时成立来确定是否满足比例关系。如果这两个等式都成立,那么就返回 1,表示满足比例关系;否则返回 0,表示不满足比例关系。

(3.)函数 generate_Search

  • 函数功能和目的
    • 该函数的主要目的是生成所有由 19 组成的三位数 ijk 的组合,并检查这些组合是否满足特定的比例关系(由 abc 定义)以及是否由九个不同的数字组成(通过调用 all_Different 函数检查)。如果满足条件,就将这些三位数打印出来;如果遍历完所有可能的组合都没有找到满足条件的组合,就打印出 “No!!!”。
  • 函数内部逻辑
    • 首先定义了一个整型变量 found,并初始化为 0,用于标记是否找到了满足条件的组合。
    • 然后通过三层for循环来枚举所有由19组成的三位数组合:
      • 最外层循环 for (int i = 123; i <= 987; i++):遍历所有可能的第一个三位数 i
      • 中间层循环 for (int j = i + 1; j <= 987; j++):遍历所有可能的第二个三位数 j,这样可以保证 j 大于 i,避免重复的组合。
      • 最内层循环 for (int k = j + 1; k <= 987; k++):遍历所有可能的第三个三位数 k,这样可以保证 k 大于 j,进一步避免重复的组合。
    • 对于每一组生成的三位数组合ijk,通过if语句进行条件判断:
      • 如果 check_Connextion(i, j, k, a, b, c)(即满足特定的比例关系)且 all_Different(i, j, k)(即由九个不同的数字组成)这两个条件同时成立,那么就将这三个三位数打印出来,并将 found 变量设置为 1,表示已经找到了满足条件的组合。
    • 最后,如果循环结束后 found 变量仍然为 0,这就意味着没有找到满足条件的组合,此时就打印出 “No!!!”。

三.总结

暴力枚举其实本质上就是对所有可能情况已以判断,但这样当所判断的数量过于庞大时很耗时耗力,对于简单,少量运行次数的数据来说是很简单方便的,但对需要大量处理的数据不适用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值