Monotonic Queue Topic
Template of Monotonic Queue Luogu P1886
output the extreme values pre k k k elements
#include<bits/stdc++.h>
using namespace std;
int n, k;
int h = 0, t = -1;
int a[1000010], q[1000010];
int main(){
cin >> n >> k;
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
for(int i = 1; i <= n; i ++){
while(h <= t && a[i] <= a[q[t]]) t --;
q[++ t] = i;
while(q[h] < i - k + 1) h ++;
if(i >= k) cout << a[q[h]] << ' ';
}
cout << '\n';
h = 0, t = -1;
for(int i = 1; i <= n; i ++){
while(h <= t && a[i] >= a[q[t]]) t --;
q[++ t] = i;
while(q[h] < i - k + 1) h ++;
if(i >= k) cout << a[q[h]] << ' ';
}
return 0;
}
A135 限制长度的最大连续子序列
Meaning of Question :
长度为 n 的数组,输出长度不超过 m 的最大子序列之和
维护前缀和,长度为 m m m 的单调队列维护前缀和最小值
int n, m, a[300010];
int h = 0, t = -1, q[300010];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin >> a[i];
a[i] += a[i - 1];
}
int mx = -2e9;
f[0] = 0;
q[++ t] = 0;
for(int i = 1; i <= n; i ++){
f[i] = a[i] - a[q[h]];
mx = max(mx, f[i]);
while(h <= t && a[i] <= a[q[t]]) t --;
q[++ t] = i;
while(h <= t && q[h] < i - m + 1) h ++;
}
cout << mx;
return 0;
}
A1089 烽火传递
每 m m m 个元素必须选一个元素,维护最小代价
f i f_i fi 表示取第 i i i 个元素的合法方案的最小代价
f i = f j + a i f_i=f_j+a_i fi=fj+ai , j ∈ [ i − m , i − 1 ] j\in [i-m,i-1] j∈[i−m,i−1]
状态转移 : [ i − m , i − 1 ] [i-m,i-1] [i−m,i−1] 里面必须选一个元素,根据选哪个来更新维护状态
int n, m, a[200010], f[200010];
int h = 0, t = -1, q[200010];
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
f[0] = 0;
q[++ t] = 0;
for(int i = 1; i <= n; i ++){
f[i] = f[q[h]] + a[i];
while(h <= t && f[i] <= f[q[t]]) t --;
q[++ t] = i;
while(h <= t && q[h] < i - m + 1) h ++;
}
int res = 2e18;
for(int i = n - m + 1; i <= n; i ++){
res = min(res, f[i]);
}
cout << res << '\n';
}
A1090 绿色通道
二分每 x x x 段就必须选一个元素,显然 x x x 越小,代价越大,具有单调性,可以二分维护。(发现每 x x x 段就选必须选一个元素, 实际就是烽火传递那道题目)
二分的 check
用单调队列优化 DP 维护,计算最小代价是否会超过题目给定的界限
void solve(){
int n, s, h, t;
cin >> n >> s;
std::vector<int> a(n + 5), f(n + 5), q(n + 5);
bool fg = false;
for(int i = 1; i <= n; i ++){
cin >> a[i];
if(s >= a[i]) fg = true;
}
if(fg == false){
cout << n << '\n';
return ;
}
/*
每 x 段必须选一个, 烽火传递
*/
auto check = [&] (int x) -> bool {
h = 0, t = -1;
q[++ t] = 0;
f[0] = 0;
for(int i = 1; i <= n; i ++){
f[i] = f[q[h]] + a[i];
while(h <= t && f[i] <= f[q[t]]) t --;
q[++ t] = i;
while(h <= t && q[h] < i - x + 1) h ++;
}
int tmp = 2e17;
for(int i = n - x + 1; i <= n; i ++){
tmp = min(tmp, f[i]);
}
return (bool)(tmp <= s);
};
int l = 1, r = n + 1;
while(l < r){
int mid = l + r >> 1;
if(check(mid)){
r = mid;
}
else{
l = mid + 1;
}
}
cout << l - 1;
}
AC1087 修剪草坪
最多连续选 k k k 个使效率最大,可以转换为 : 每 k + 1 k+1 k+1 个里面必须选一个不选,使不选的效率之和最小,然后用总效率减去不选的最小效率就是答案(补集思想)
状态 : f i f_i fi 表示考虑 1 ∼ i 1\sim i 1∼i 个奶牛,选第 i i i 个的合法方案的最小效率
转移 : f i = m i n ( f j ) + w i f_i=min(f_j)+w_i fi=min(fj)+wi , i − m ≤ j ≤ i − 1 i-m\leq j\leq i-1 i−m≤j≤i−1 , m = k + 1 m=k+1 m=k+1
边界 : f 0 = 0 f_0=0 f0=0
答案 : s u m − m i n ( f [ n − m + 1 ∼ n ] ) sum-min(f[n-m+1\sim n]) sum−min(f[n−m+1∼n])
还是转化到烽火传递这道题目上
int n, k, s, a[100010];
int h = 0, t = -1;
int f[100010], q[100010];
void solve(){
cin >> n >> k;
k ++;
for(int i = 1; i <= n; i ++){
cin >> a[i];
s += a[i];
}
f[0] = 0, q[++ t] = 0;
for(int i = 1; i <= n; i ++){
f[i] = f[q[h]] + a[i];
while(h <= t && f[i] <= f[q[t]]) t --;
q[++ t] = i;
while(h <= t && q[h] < i - k + 1) h ++;
}
int res = 2e18;
for(int i = n - k + 1; i <= n; i ++){
res = min(res, f[i]);
}
cout << s - res << '\n';
}