这道题目来自《算法竞赛进阶指南》的前缀和与差分章节,属于典型的区间修改与单点查询问题。题目描述的是有N头牛站成一排,给出其中最高的牛的身高H,以及M对关系(A,B),表示A和B可以互相看见(意味着A和B之间的牛都比它们矮)。要求我们确定所有可能的牛身高序列中,满足条件的最高牛的身高。
在实际竞赛中,这类问题经常出现在需要高效处理区间操作的场景。比如在游戏开发中处理地形高度差,或者在数据分析中处理时间序列的区间统计。理解这类问题的解法,对提升算法思维和编码能力都很有帮助。
这道题的关键在于如何高效处理大量的区间修改操作。直接遍历每个区间进行修改的时间复杂度是O(M*N),在N和M较大时(比如1e5量级)会超时。这时候差分数组就派上用场了。
差分数组的核心思想是:对于区间[l,r]的统一修改操作,我们只需要在差分数组的l位置加上变化量,在r+1位置减去变化量。最后通过前缀和还原出原数组。这样就把O(N)的区间操作降为了O(1)的单点操作。
这里的关键是第三步的实现。直接遍历区间修改显然不够高效,我们需要使用差分数组来优化。
cpp复制#include <iostream>
#include <map>
using namespace std;
const int MAXN = 1e4 + 10;
int diff[MAXN]; // 差分数组
map<pair<int,int>, bool> existed; // 记录关系是否已存在
int main() {
int N, P, H, M;
cin >> N >> P >> H >> M;
// 初始化差分数组
diff[1] = H;
for(int i = 2; i <= N; i++) {
diff[i] = 0;
}
while(M--) {
int A, B;
cin >> A >> B;
if(A > B) swap(A, B);
if(existed[{A,B}]) continue; // 去重
existed[{A,B}] = true;
// 区间[A+1,B-1]减1
diff[A+1]--;
diff[B]++;
}
// 通过前缀和还原原数组
int current = 0;
for(int i = 1; i <= N; i++) {
current += diff[i];
cout << current << endl;
}
return 0;
}
差分数组初始化:我们初始化diff[1]=H,其余为0。这样在计算前缀和时,所有牛初始身高都是H。
关系去重处理:使用map记录已经处理过的关系对,避免重复处理影响结果。
区间修改操作:对于区间[A+1,B-1]的减1操作,转换为在差分数组的A+1位置减1,B位置加1。
前缀和还原:最后通过计算差分数组的前缀和,得到每头牛的最终身高。
提示:在竞赛中遇到这类题目,建议先在纸上画出差分数组的变化过程,确保完全理解其工作原理后再编码。
差分数组技巧不仅适用于这类"牛的高度"问题,还可以应用于:
对于第一个扩展问题,我们可以在最后找出数组中的最大值,然后将所有等于这个值的牛标记为最高。第二个问题需要为每对关系存储不同的差值。第三个问题需要考虑环形数组的处理,可能需要特殊处理跨过起点和终点的区间。
在算法竞赛中,差分数组是一个非常重要的技巧,尤其是在需要高效处理大量区间操作的场景。以下是一些实战心得:
模板化思维:将差分数组的实现模板化,可以快速应用到不同题目中。我的常用模板包括初始化、区间修改和前缀和还原三个部分。
注意索引:差分数组通常使用1-based索引更直观,但要注意与题目描述的对应关系。
去重很重要:在实际问题中,区间描述可能有重复,一定要记得去重,否则会导致多次修改。
结合其他数据结构:有时差分数组可以和其他数据结构如线段树、树状数组结合使用,解决更复杂的问题。
逆向思维:有些问题需要逆向使用差分数组,比如先计算前缀和数组,再通过差分得到原数组。
在实际比赛中,我遇到过一道类似题目,因为没有处理重复关系导致WA了两次。后来通过添加map去重才通过。这个教训让我意识到,即使题目描述没有明确说明,也要考虑输入数据可能有重复的情况。