Codeforces 363B:Fence 滑动窗口与下标转换
这道题真正容易错的地方不是滑动窗口本身,而是:题目用 1-based 下标描述答案,C++ 数组却从 0 开始。先把窗口和下标写清楚,再写循环。
1. 题目在求什么
有 n 块木板,高度为 h₁, h₂, ..., hₙ。要求找到长度固定为 k 的连续区间,使区间和最小,并输出这个区间的起点 j。
题目中的窗口是:
h[j] + h[j+1] + ... + h[j+k-1]
这里的 j 从 1 开始。比如 n = 5, k = 3 时,合法窗口是:
j = 1:h1 + h2 + h3
j = 2:h2 + h3 + h4
j = 3:h3 + h4 + h5
窗口长度始终是题目给出的 k,不是从某个区间端点推出来的长度。
2. 1-based 题目下标和 0-based C++ 下标
| 含义 | 题目 1-based | C++ 0-based |
|---|---|---|
| 第一个元素 | h₁ |
h[0] |
| 窗口左端 | j |
left = j - 1 |
| 窗口右端 | j + k - 1 |
right = j + k - 2 |
如果滑动窗口右端在代码中记为 i,那么 i 是 0-based。长度为 k 时,窗口左端是:
left = i - k + 1;
题目要求输出 1-based 起点,所以要再加 1:
j = left + 1
= (i - k + 1) + 1
= i - k + 2
因此必须写:
int pos = i - k + 2;
这个 +2 不是死记,而是“先算 0-based 左端,再转换回 1-based”的结果。
3. 为什么 int k = y - x + 1 是错误的
错误思路类似:
int k = y - x + 1;
这把某个区间 [x, y] 的长度当成了窗口长度。可是本题的 k 是输入中给出的固定长度,窗口应该在整个数组上移动:
[0, k-1]
[1, k]
[2, k+1]
...
如果把 [x, y] 的长度误写成 k,程序会检查错误长度,或者根本没有按题目要求滑动。
4. 从暴力到滑动窗口
暴力方法会对每个起点重新累加 k 个数,复杂度是 O(nk)。但相邻窗口只差两个元素:
旧窗口:h[j] + h[j+1] + ... + h[j+k-1]
新窗口:h[j+1] + h[j+2] + ... + h[j+k]
所以每次只需要:
window_sum -= h[i - k];
window_sum += h[i];
先计算第一个窗口,再让右端从 k 开始移动,总复杂度降为 O(n)。
5. 正确的 C++ 实现
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<int> h(n);
for (int &x : h)
{
cin >> x;
}
int window_sum = 0;
for (int i = 0; i < k; ++i)
{
window_sum += h[i];
}
int best_sum = window_sum;
int best_pos = 1; // 第一个窗口的题目下标是 1
for (int i = k; i < n; ++i)
{
window_sum -= h[i - k];
window_sum += h[i];
if (window_sum < best_sum)
{
best_sum = window_sum;
best_pos = i - k + 2;
}
}
cout << best_pos << '\n';
return 0;
}
循环中的 i 是新加入窗口的右端:
旧窗口:h[i-k] ... h[i-1]
新窗口:h[i-k+1] ... h[i]
所以删除 h[i-k],加入 h[i]。新窗口左端是 i-k+1,输出题目位置时就是 i-k+2。
6. 用例子验证下标
n = 5, k = 3
h = [4, 2, 7, 1, 3]
三个窗口:
代码 [0, 2]:4 + 2 + 7 = 13,题目起点 j = 1
代码 [1, 3]:2 + 7 + 1 = 10,题目起点 j = 2
代码 [2, 4]:7 + 1 + 3 = 11,题目起点 j = 3
最小和是 10,所以输出 2。处理第二个窗口时 i = 3:
i - k + 2 = 3 - 3 + 2 = 2
正好是题目的 1-based 起点。
7. 相等时的处理
代码使用:
if (window_sum < best_sum)
而不是 <=,因此多个窗口和相等时保留最先出现的位置。如果题目只要求任意最优位置,<= 也可能通过,但它会改成保留最后一个位置,必须确认题意后再选择。
8. 复杂度和边界
- 初始化第一个窗口:
O(k); - 每个后续元素只进出窗口一次:总时间
O(n); - 数组存储:
O(n),窗口变量本身是O(1)。
提交前检查:
k = 1:每个单元素都是窗口;k = n:只有一个窗口,答案是1;- 最小和在第一个或最后一个窗口时,位置是否正确;
- 相等窗口是否需要最左位置;
- 高度和较大时,把
window_sum、best_sum改成long long。
9. MinGW 的 WinMain 报错
如果看到:
undefined reference to `WinMain'
先不要改算法。这个错误通常说明链接器把程序按 Windows GUI 子系统处理了,而代码提供的是普通控制台入口 main。可以明确指定控制台子系统:
g++ -std=c++20 -mconsole 363B.cpp -o 363B.exe
同时检查 VS Code 任务或项目设置中是否错误带有 -mwindows。-static 主要解决运行时库依赖,并不是修复 WinMain 选择的首要选项:
g++ -std=c++20 363B.cpp -o 363B.exe -static
如果仍然报 WinMain,回到 -mconsole 或移除 -mwindows,不要继续修改 C++ 逻辑。
10. 一句话总结
固定长度
k的窗口在 0-based 数组上滑动;右端为i时,窗口左端为i-k+1,题目要求的 1-based 起点就是i-k+2。