多重背包单调队列优化思路_单调队列(多重背包优化铺垫)

我为什么想写这篇单调队列的总结

一个原因是我在在看背包问题里的 多重背包解题优化 的时候后看到了 一种算是我现在知道最高效的一个优化方法,了解到可以用 单调队列优化 去大大的减小运算复杂度,然后就到百度上搜索学习,看到了一些 较好的博文、讲解视频,所以有我写的这篇一些东内容是从中借用的,但是大部分文章还讲的 有难以理解。所我想先总结一下这个单调队列的用法,并以我认为简单的方式总结好,并希望能帮助其它的人

单调队列是什么、怎么理解

从字面单调我们可以知道它就是 从大到小或者从小到大 排列的一组连续的数.我认为他最重要的核心是:它所确定的 单调性 一定是在一定是 在当前的某个限制个范围上的(他最重要的就是这个 范围限制,否则的话 直接找就好了,何必这么麻烦呢),在这个范围上 有最大或最小值,这是最重要的它的应用也是建立在这个基础上的。我认为 也可以把单调队列看作是一个解题的思想来看待它。

单调队列有什么用

先举个🌰去感受它的 操作过程

举个例子:有 7 6 8 12 9 10 3 七个数字,现在让你找出范围 [ i-3,i ] (i<=1 && i <=7) 的最小值。

我们先模拟一遍

这里注意我们可以建立 一个 单调递减的数列,这个队列从左往右的是递减的(我们默认队列列的最 左边 是头部,最右边是尾部)所以这个队列的的部元素一定是该队列中的最大值,当我们在插入元素的时候,一定是从队列的尾部插入元素,当插入元素后我们 需要进行 两步判断操作:

操作 1.维持(保持)队列的单调性:这个明词说的好像说的非常高端、难以理解,其实它的意思非常容易理解「例如:在一个递减队列 {5,3,1}中我们插入 一个4,则这个队列就变成了{5,3,1,4},这个时候队列的单调性已经被破坏了,为了使队列仍具有单递增性 这个时候就需要把 在队列中小于等于4的元素全部弹出,这队列就变成了{5,4},队列又恢复了原来的单调递增性,即使经过n次这样的操作 我们仍然可以确定 队头元素一定是最大值(最左边的元素)」,这一个操作存在的意义是不管怎么从尾部插入元素,经过这部操作后队列一定仍是 有序的。

.

操作 2.检查 队列中的元素是否都在所规定的边界范围内:这个直接说还真的不一定能理解「所以还是继续举例子:假如我们有一个 序列 5 、3、1、4 我们 要求3,1,4 这个三个数的最🀄️的大值,我们如果把 5、3、1、4 按照 1操步骤 插入单调队列中去得到单调队列为 {5,4} 这个时候他的最大值是5,但是我们是取不到 5 这个数的,因为我们让求的是3、1、4 这三个数范围内的最大值,显然这个时候 5已经越界了,所以要把越界的头部元素弹出队列 ,这样队列剩余的元素为 {4},4正是我们所求的最大值 」我想这个操作步骤存在的意义 就是把 不在规定所求区域的队列中的头部元素剔除,使头部(最大、最小值)元素一定在所求区域

操作过程模拟

目标序列

7

6

8

12

9

10

3

序列元素下表 i(区间)

1

2

3

4

5

6

7

起始的时候 递增队列为空的

i 值

所求区间

插入元素后的队列

操作1后的队列

操作2后的最终队列

1

(-2,1)

{ 7 }

{ 7 }

{ 7}

2

(-1,2)

{ 7,6}

{ 6 }7大于6舍去

{ 6}

3

(0,3)

{ 6,8 }

{ 6,8 }

{ 6,8 }

4

(1,4)

{6,8,12}

{ 6,8,12}

{6,8,12}

5

(2,5)

{6,8,12,9}

{6,9}8,12大于9舍去

{6,9}

6

(3,6)

{6,9,10}

{6,9,10}

{9,10} 6越界舍去

7

(4,7)

{9,10,7}

{7} 9,10大于7舍去

{7}

所以经过 这样几次循环的操作和最终的序列为{ 7 }

单调队列作用

**单调队列用于维护一个长度固定的区间内,数组的最值。以最大值为例,如果一个数组长度为n,取长度为m的区间,那么单调队列的队首一定是数组在该区间内的最大值。之所以是维护,就是当区间开始整体后移时,最大值可能发生变化,而单调队列可以在O(n)的时间复杂度下得到全部n-m+1个区间的最大值。

**

单调队列实例解析 :最大子序和

输入一个长度为n的整数序列,从中找出一段长度不超过m的连续子序列,使得子序列中所有数的和最大。

注意: 子序列的长度至少是1。

输入格式

第一行输入两个整数n,m。

第二行输入n个数,代表长度为n的整数序列。

同一行数之间用空格隔开。

输出格式

输出一个整数,代表该序列的最大子序和。

数据范围

1≤n,m≤300000

输入样例:

6 4

1 -3 5 1 -2 3

输出样例:

7

思路如下:

我们先把序列的前i项和加起来并存到一个数组sum[ ]上,那么任意连续的子序列和就为sum [ i ] - sum[ j ] (i>j && j>i-m)。

那么我们就可以跟上述例子一样枚举i值 当i值确定则sum[i]也确定,在当前sum[i]值已经确定确定的情况下,每次找出在(i-m,i)范围内的最小sum[j]值(这正照应:在当前的限制范围内,不断更新单调队列)。

题解如下 一:

#include

#include

using namespace std;

const int Len = 300005;

long long int sum[Len];

int main()

{

int n,m;

scanf("%d%d",&n,&m);

for(int i = 1;i <= n;i ++)//求前缀和

{

cin>>sum[i];

sum[i] += sum[i - 1];

}

//初始化数据

int left = 1,right = 1;

int index[Len] = {0};

index[1] = 1;

long long int maxx = sum[1];

//(定范围的)单调递增序列

for(int i = 2;i <= n;i ++)

{

//操作1:检查 单调队列 最左边的元素 是否不再(i - m, i)这个范围内

if(index[left] < i - m)

left ++;

//用当前sum[i]减去前 单调队列中 最左边的 最小sum值

maxx = max(maxx,sum[i] - sum[index[left]]);

//操作2:往 队列 最右端添加sum[i]元素(实际上是把 i 坐标添加进单调队列了),为了维护 队列从小到大的单调性,所以要把 队列中元素 比 sum[i] 大的都删去

while(left <= right && sum[index[right]] >= sum[i])

right --;

//添加sum[i]元素的下表

right ++;

index[right] = i;

}

printf("%lld\n",maxx);

return 0;

}

题解如下 二:

#include

#include

#include

using namespace std;

const int L = 300005;

long long int s[L];

int n,m;

int q[L];

int main()

{

cin>>n>>m;

s[0] = 0; //把 首元素设置为边界值,去解决边界问题

for(int i = 1;i <= n;i ++)

{

cin>>s[i];

s[i] += s[i-1];

}

//初始化数据

long long int mx = INT_MIN;

int hd = 0,ed = 0;

//q[0] = sum[

for(int i = 1;i <= n;i ++)

{

if(i - q[hd] > m) hd ++;//操作2

mx = max(mx,s[i] - s[q[hd]]);

while(hd <= ed && s[i] <= s[q[ed]]) ed --;//操作1

q[++ed] = i;

}

cout<

}

题解如下 三:

#include

#include

#include

#include

#include

using namespace std;

const int L = 300005;

long long int sum[L];

int n,m;

deque qu;

int main()

{

cin>>n>>m;

sum[0] = 0;

for(int i = 1;i <= n;i ++)

{

cin>>sum[i];

sum[i] += sum[i-1];

}

long long int mx = INT_MIN;

qu.push_back(0);

for(int i = 1;i <= n;i ++)

{

if(i - qu.front() > m) qu.pop_front();//操作2检查边界

mx = max(mx,sum[i] - sum[qu.front()]);

while(!qu.empty() && sum[i] <= sum[qu.back()]) qu.pop_back();//操作1 维护队列单调性

qu.push_back(i);

}

cout<

return 0;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值