【上交ACM-算法初级】枚举

枚举
  • 枚举法是一种通过枚举所有可能解,检查该可能解是否符合要求,并将符合要求的解计入答案的方法。

  • 在解决问题的过程中,我们需要枚举的对象有很多种,比如数值、区间、矩形、日期等等。

  • 在设计枚举算法时,一些思路直接的算法虽然很容易理解,但是通常会导致高昂的时间代价。所以我们可以通过加入数学计算、并且存储尽可能多的信息的方法,来降低时间复杂度。

  • 严谨描述一下枚举法的过程:

  1. 确定枚举对象、枚举范围和判定条件;

  2. 枚举可能的解,验证是否是问题的解。

 
枚举优化

在上面的做法中,我们给出的都是最简单直接的算法。但是当复杂度为O(n^2)O(n2)时,个人电脑在1s之内可能只能处理最多10^4104的数据规模,而当算法复杂度到O(n^4)O(n4)时,可能数据规模最多只能是10^2102,所以非常低效。所以,下面我们给出一些枚举算法的优化思路:

能算则算

可以通过必要的计算规避一些不必要的枚举。

比如在上面的统计矩形的例子中,我们枚举左上角之后,长方形和正方形满足条件的右下角个数可以通过计算得出。

所以,我们可以通过以下计算来统计(i, j)(i,j)左上角对应对长方形和正方形的贡献

 

 

能存则存

可以通过储存更多的信息来避免重复计算。

在上面的序列染色的例子中,算法的瓶颈在于对于一个确定的绿颜色区间,如何快速计算需要修改的颜色个数。考虑到该步骤是在询问一个区间上的信息。

【前缀和优化】

  • 什么是前缀?原数组的第ii个前缀指的是第11个到第ii个的一段。比如原数组为
(1, 2, 3, 4, 5, 6, 7)

​ 则该数组的8个前缀分别为

  • 什么是前缀和数组?仍然假设原数组为
int a[] = {1, 2, 3, 4, 5, 6, 7};

那么,对于前缀和数组的第ii个元素,它的值就是原数组 1~i 这个前缀所有元素的和。所以该数组的前缀和数组为:

int sum[] = {1, 3, 6, 10, 15, 21, 28};
  • 前缀和数组有什么作用?可以看到,如果我们想求原数组第ii个元素到第jj个元素的和时,只需输出sum[j] - sum[i - 1]即可。

枚举区间

要想枚举满足条件的所有区间,最常见的枚举方法就是分别枚举区间的左右端点。

举例: 序列染色

有连续N个格子。起初每个格子分别被染成了R(红色)G(绿色)B(蓝色)三种不同颜色,问最少改变多少个格子的颜色,使得这N个格子可以被分成R、G、B的三段,且每一段长度不为空。

如下图的例子,第一行的格子通过将第三个涂成红色,第六个涂成蓝色,变成了一行RGB的形式。

思路

因为满足RGB条件的格子染色方案之间,区别在于位于中间的绿色区间的位置。

我们可以设计如下算法:

 

在上述思路中,我们枚举的对象是“绿色区间的位置”,需要检查的条件是“需要修改的格子数是否为目前最少的”。

下面是实现该算法的伪代码:

ans <- n;     // 最多修改不会超过n个格子
for i <- 2 to n - 1 do
    for j <- i to n - 1 do
        cnt <- 绿色区间为[i, j]时需要重新涂颜色的格子数
        if cnt < ans then ans <- cnt
输出 ans

复杂度

因为“cnt <- 绿色区间为[i, j]时需要重新涂颜色的格子数”是一个子过程,并且该子过程被运行了O(n^2)次,所以,整个算法的复杂度为

 

所以,一个高效的统计方法会降低整个算法的运行时间。目前,我们可以用最简单的方法:

将整个序列扫描一遍,如果当前格子的颜色和当前枚举的答案序列不一样,就让统计数值+1。

那么该子过程的复杂度是O(n)O(n),而整个算法的复杂度是O(n^3)

格子染色-枚举优化算法

使用前缀和数组,区间和可以通过两个前缀和相减快速求出。这里我们可以拓展这个思路,预处理出三个前缀和数组:

int a[N];        // 原序列

int not_R[N];    // 前i个格子里不是红色的格子个数
int not_G[N];    // 前i个格子里不是绿色的格子个数
int not_B[N];    // 前i个格子里不是蓝色的格子个数

for (int i = 1; i <= n; ++i) {
    not_R[i] = not_R[i - 1] + (a[i] != 'R');
    not_G[i] = not_G[i - 1] + (a[i] != 'G');
    not_B[i] = not_B[i - 1] + (a[i] != 'B');
}

这样,对于一个绿色区间[i, j],总需要修改的格子数为三个颜色区间里,不等于各自颜色的格子数量求和:

n_change = not_R[i - 1] + (not_G[j] - not_G[i - 1]) + (not_B[n] - not_B[j]);

 

posted @ 2022-04-24 22:01  Teddyonthebench  阅读(75)  评论(0编辑  收藏  举报