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()];
}
最后,我们知道了每个位置要刷的高度,要求刷的次数。则用贪心可求出:
- 当 \(window[i] \neq window[i+1]\;\)
ans++ - 每隔 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! 😃