单调队列

单调队列
简单应用:
优化数列查找中的滑窗问题,原来的复杂度O(mn) (n个元素,查找的子区间m长)
但是应用单调队列来可以达到复杂度为O(n)(只遍历一遍,分析子区间是每个元素只需要进入数列一次,然后这样可以用到原来结果。

POJ2823
题意给出一个数组,给出子区间大小,然后输出子区间中的最大最小值
暴力mn会超时,然后通过开一个f[]来存放当前滑窗中的最小值,然后遍历过程中新进来的元素如果比队尾的元素进行比较
(要区间最小,则新进来的元素要比f[]队尾大,维护滑窗单调递增)
(要区间最大,则新进来的元素要比f[]队尾小,维护滑窗单调递减)
否则就把f[]的元素剔除,就不会再进入。

//
//  main.cpp
//  poj2823_m
//
//  Created by 陈冉飞 on 2019/8/8.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
#include <cstring>
using namespace std;
#define maxn 1000100
int a[maxn],f[maxn],t[maxn];    //t为滑窗数组,用来储存位置
int n,k;    //k为滑窗的大小

void getans(int x){
    int head = 0,tail = 0;  //初始化滑窗的首尾
    for (int i = 0; i < n; i++) {
        while (head<tail && x == 0 && f[tail-1]<a[i]) tail--;  //通过一个head和tail维护滑窗的长度,然后如果滑窗最后的元素大于要进入滑窗的下一个元素a[i],出来
        while (head<tail && x == 1 && f[tail-1]>a[i]) tail--;  //维护最大值的情况
        f[tail] = a[i];  //原数组中每个元素都可能为最值,先放入
        t[tail] = i;  //标记当前的tail位置,i是滑窗尾的位置
        tail++;  //移动tail
        while (t[head] <= i-k) head++;
        if(i > k-2) printf("%d%c",f[head],i == n-1?'\n':' ');
    }
}

int main(int argc, const char * argv[]) {
    scanf("%d%d",&n,&k);
    for (int i = 0; i < n; i++) scanf("%d",&a[i]);
    getans(1);
    getans(0);
    return 0;
}

单调队列在多重背包中的优化,多重背包在dp背包复习中有提到,利用单调队列优化时间复杂度,通过单调队列将最大值(最小值一直维护在队首)
例题hdu2191
如果直接最基本的多重背包也可以过数据比较水,然后就是用单调队列优化

//
//  main.cpp
//  多重背包_单调队列优化_hdu2191
//
//  Created by 陈冉飞 on 2019/8/10.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
int T,money_total,n,head,tail,i,j,temmoney,temweight,temnum,d;
#define maxn 7005
int dp[maxn],t[maxn],f[maxn];   //t为记录位置的,f为存放值的。

int main(int argc, const char * argv[]) {
    for (scanf("%d",&T); T; T--) {
        scanf("%d%d",&money_total,&n);
        //初始化
        cl(dp, 0),cl(f, 0);
        for (i = 1; i <= n; i++) {
            scanf("%d%d%d",&temmoney,&temweight,&temnum);
            for(int b=0;b<temmoney;b++){//枚举余数b  b = j mod moneytotal
                head=tail=1;      //清空队列
                for(int j=0;j<=(money_total-b)/temmoney;j++){
                    int tmp=dp[j*temmoney+b]-j*temweight;
                    while(head<tail&&tmp>=f[tail-1]) --tail;//先插入以满足s≤min(n,a)的条件
                    f[tail]=tmp,t[tail++]=j;
                    while(t[head]<j-min(temnum,j)) ++head;//以下j代表a,删除队首以去除不满足a−min(n,a)≤s的状态
                    dp[j*temmoney+b]=f[head]+j*temweight;//队首即为最优
                }
            }
        }
//        for (i = 0; i <= money_total; i++) cout<<i<<"  "<<dp[i]<<endl;
        cout<<dp[money_total]<<endl;
    }
    return 0;
}

scoi2010
分别在买卖股票的时候进行单调队列的优化。


模版:

for(j)  //进行遍历
{
	while(条件)head++;   //滑动的左端点增加
	while(条件)tail++;    //滑块的右端点增加
	t[++tail] = j;   //对记录位置的数组进行调整
	/*
	 *记录或者刷新最值或者输出
	 */
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值