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)。

提交前检查:

  1. k = 1:每个单元素都是窗口;
  2. k = n:只有一个窗口,答案是 1;
  3. 最小和在第一个或最后一个窗口时,位置是否正确;
  4. 相等窗口是否需要最左位置;
  5. 高度和较大时,把 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。