Monotonic_Queue_1

文章讲述了如何利用单调队列(MonotonicQueue)的数据结构解决一系列与区间选择、前缀和、子序列和有关的问题,如最大连续子序列和、最小代价维护、烽火传递和修剪草坪等,通过状态转移和二分技巧优化求解过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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[im,i1]

状态转移 : [ i − m , i − 1 ] [i-m,i-1] [im,i1] 里面必须选一个元素,根据选哪个来更新维护状态

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 1i 个奶牛,选第 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 imji1 , 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]) summin(f[nm+1n])

还是转化到烽火传递这道题目上

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';
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值