P5186 [COCI 2009_2010 #4] OGRADA

Luogu 题目传送门

双倍经验,一摸一样的题:Luogu 题目传送门2

这是我的一个有趣的做题故事,在洛谷打完卡(运势 § 凶 § )后,随机跳转一题,并到了这一题。

\(\;\)

题目大意

一个叫 Matija 的人打算用一个为 X 大小的滚筒刷一个栅栏。其中,这个栅栏是由 N 条木板,从左到右依次编号为 1…N,i 号木板的高度为 \(h_i\)。 Matija 是从上往下尽可能的刷最大面积的漆,剩下刷不到的面积用牙刷☠️刷。我们需要求出用牙刷 「涂」 的面积+在满足 「涂」 的面积最少的情况下,他最少要用滚筒刷 「刷」 多少次。

输入样例:

5 3
5 3 4 4 5

样例

一共有 5 个板子,高度分别为:5 3 4 4 5, 和一个刷子大小为 3 。Matija 会用刷子两次刷1号、2号、3号板子 \(3\) 格子的高度 和 3号、4号、5号板子 \(4\) 格子的高度。剩下的面积为 \(3\) 格子。

输出为:

3
2

算法!

用滑动窗口!

P1886 滑动窗口模板传送门

把问题分成两个 Parts。

Part 1

首先我们做一个行列式叫做 d ,然后从 1 到 N 遍历一遍,每次查询 a[i] 的时候检查 d 里的第一块木板 d.front() 移除木板如果超出窗口范围,i-d.front()+1为窗口大小。

if (!d.empty()&&i-d.front()+1>X) d.pop_front();

\(\;\)

然后移除全部 ≥ a[i] 的木板。有些人问为什么要这样做,答:这样可以维持第一块木板 d.front() 在本次窗口最小,相当于 \(min(a[i]) \; \{i\sim i+k-1\}\)

while (!d.empty()&&a[d.back()]>=a[i]) d.pop_back();

\(\;\)

if (i>=X) 表明可以开始刷了,用 window[i] 记录当前窗口最小值(换句话说:当前窗口的有效高度)。当前窗口的有效高度 \(\ast\) 刷子的长度 = 每个木板能涂的最大高度: window[i]*X。但是每个窗口大概率会有重叠的部分,比如像以下照片。
重叠

\(\;\)

这两个虚线画的为两个窗口,被涂的部分为重叠部分。重叠部分算法为选更矮的木板 min (新的木板高度,上一个窗口的有效高度) 乘以重叠部分的长度 X-1

min(window[i-1],window[i])*(X-1)

最后在用总长度 - 刚刚计算出来的长度就是第一小问的答案:high-num

\(\;\)

Part 2

用测试样例我们得到的 windows[0,0,3,3,4]

但是这不是每个板子的有效高度,比如 1 号板子有效高度是 3 不是 0, 3号、4号板子 有效高度是 4 而不是 3 。

如果仔细看,会发现 2 \(\ast\) k-1 个连起来的板子的中间板子,列如 i ~ i+2 \(\ast\) k-1 号板子的第 i+k号板子的有效高度是为 min(a[i]) {i~i+2 \(\ast\) k-1}。

而且在 Part 1 我们已经得到了每个窗口左边的有效高度,所以我们只需要从右往左查询一边把 window[i] 设为 min(window[j]) {j-k~j \(\leq\) i}。这样就可以把 windows 改成每个木板的有效高度 [3,3,4,4,4]

code:

for (int i=N;i>=1;i--){
  if (!d2.empty()&&d2.front()-i+1>X) d2.pop_front();
  while (!d2.empty() && window[d2.back()]<=window[i])
      d2.pop_back();
  d2.push_back(i);
  window2[i]=window[d2.front()];
}

最后,我们知道了每个位置要刷的高度,要求刷的次数。则用贪心可求出:

  1. \(window[i] \neq window[i+1]\;\) ans++
  2. 每隔 k 个窗口\(\;\) ans++

最后的最后,不开 long long 见祖宗!

时间复杂度:O(3N) = O(N)

废话不多说,代码直接上

完整代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e6;

int N,X,a[maxn],high,window[maxn],window2[maxn],num,ans,cnt;

signed main() {
    cin>>N>>X;
    for (int i=1;i<=N;i++) cin>>a[i],high+=a[i];

    deque<int> d,d2;
    for (int i=1;i<=N;i++) {
        if (!d.empty()&&i-d.front()+1>X) d.pop_front();
        while (!d.empty()&&a[d.back()]>=a[i]) d.pop_back(); // 移除比最后一个长的

        d.push_back(i); // 新的
        if (i>=X) {
            window[i]=a[d.front()];
            num+=window[i]*X;
            num-=min(window[i-1],window[i])*(X-1);
        }
    }

    for (int i=N;i>=1;i--){
        if (!d2.empty()&&d2.front()-i+1>X) d2.pop_front();
        while (!d2.empty() && window[d2.back()]<=window[i]) d2.pop_back();
        d2.push_back(i);
        window2[i]=window[d2.front()];
    }

    cout<<high-num<<endl;
    num=-1;
    for (int i=1;i<=N;i++){
        if (window2[i]!=window2[i-1]||i>=num+X) {
            ans++;
            num=i;
        }
    }
    cout<<ans;
    return 0;
}

我的提交记录

That's ALL! 😃

posted @ 2025-12-04 13:16  ProJon  阅读(0)  评论(0)    收藏  举报