这道题目来自《算法竞赛进阶指南》的前缀和与差分章节,题目名为"最高的牛"。题目描述如下:
有N头牛站成一排,编号从1到N。我们知道其中最高的牛是第P头,高度为H。现在给出M对关系(A,B),表示牛A和牛B可以互相看见(即它们之间的牛都比它们矮)。要求根据这些信息,确定每头牛可能的最大高度。
这个问题的核心在于如何利用给定的约束条件,推导出每头牛可能达到的最大高度。乍看之下似乎需要复杂的推理,但实际上通过巧妙运用前缀和与差分技巧,可以高效优雅地解决。
首先我们需要明确几个关键观察:
差分数组是前缀和的逆运算,它能够高效处理区间增减操作。具体来说:
在这个问题中,我们可以:
需要注意的一个细节是关系对(A,B)可能有重复,或者以(B,A)形式出现(A>B)。因此在实际处理时:
cpp复制const int MAXN = 10010; // 假设最大牛数
int d[MAXN]; // 差分数组
int n, p, h, m; // 牛数、最高牛编号、高度、关系数
set<pair<int,int>> seen; // 记录已处理的关系
// 初始化
cin >> n >> p >> h >> m;
d[1] = h; // 初始差分设置
cpp复制for(int i=0; i<m; i++) {
int a, b;
cin >> a >> b;
if(a > b) swap(a, b); // 确保a < b
// 检查是否已处理过该关系
if(seen.count({a,b})) continue;
seen.insert({a,b});
// 应用差分操作
d[a+1]--;
d[b]++;
}
cpp复制int current = 0;
for(int i=1; i<=n; i++) {
current += d[i]; // 前缀和计算
cout << current << endl;
}
如果牛的数量n很大(比如1e6),可以:
这种区间增减+前缀和的方法在以下场景也很常见:
cpp复制#include <iostream>
#include <set>
using namespace std;
const int MAXN = 10010;
int d[MAXN]; // 差分数组
int main() {
int n, p, h, m;
cin >> n >> p >> h >> m;
set<pair<int,int>> seen;
d[1] = h;
for(int i=0; i<m; i++) {
int a, b;
cin >> a >> b;
if(a > b) swap(a, b);
if(seen.count({a,b})) continue;
seen.insert({a,b});
if(b == a+1) continue; // 相邻不需要处理
d[a+1]--;
d[b]++;
}
int current = 0;
for(int i=1; i<=n; i++) {
current += d[i];
cout << current << endl;
}
return 0;
}
这道题很好地展示了差分技巧在解决区间操作问题时的威力。在实际编程竞赛中,很多看似复杂的问题都可以通过适当的预处理(如差分数组)转化为简单高效的计算。
我在最初接触这类问题时,常常会陷入如何"直接"计算每个位置的值的思维定势。后来发现,很多问题都可以转化为:
这种思维模式不仅适用于这道题,还可以推广到许多其他场景。例如处理时间区间、资源分配等问题时,都可以考虑类似的思路。
最后提醒一点:在使用差分数组时,务必注意下标范围和边界条件。在实际编程中,我习惯将数组大小多开几个元素,以避免潜在的越界问题。同时,对于区间操作,清晰的注释和变量命名也能大大减少出错概率。