算法竞赛刷题笔记 iv

单调队列的part i

POJ 2823 sliding window

在这里插入图片描述
我的想法: 先只考虑最大值,如果滑动窗口的大小为1,那么每个数都是窗口的最大值。如果滑动窗口为2,如何通过上一个窗口来获得当前的窗口的信息,如(a b) c–>a (b c) 当前窗口为(b c),假设上一个窗口的最大值为b,那么我们可以用上上个窗口的最大值来作为信息,如果b不是最大值,那么我们必须要用的b,窗口为2看不出有什么信息加速的功能。当窗口为3 (a b c) d —> a (b c d) 如果最大值在b或者c 那么将(a b c)的最大值与d进行比较就能得到(b c d)的最大值 如果a是最大值,那么我们需要得到b c的最大值,而不需要比较b c,那么我们就可以减少比较从而达到加速效果。想到的方法一 是全局记录最大的两个数,当最大值离开窗口范围,那么第二大的值就补上,问题是第三大的值在哪里找?方法一的失败 得到了记录的数据结构应不是定长的。
数据结构有的特点是1、能找到最大值,并且2、能找到超出范围的值,而且3、能尽快的找到下一个最大的值,CASE A:如果是堆,1,3都能满足,但是2满足的需要在每个数那里记录序号,当序号超界直接弹出,找到下个最大值的时间需要O(logn),插入一个新元素O(logn)。CASE B:如果是单调数组,1,3都能满足,2满足也是需要记录序号,找到下一个最大值需要O(1),如果插入一个新元素也只需要O(1)的时间,那么这个解就是线性,也是最优解,如果每次插入后排序,那么不能O(1),因为要有序,所以剩下的情况只有将数组中一部分值永久删除来达到排序效果,现在就考虑是否队列中有一部分值是当元素插入后永远用不到的,只有两种可能,一种是大于插入元素,一种小于插入元素,大于肯定不能删,那么就考虑小于的,对于序号i<j 但num[j] > num[i] 那么在j之后i永远用不到,这个永远用不到的性质成立! 代码走起:
11592K 5625MS

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
using namespace std;
const int N = 1000007;
int p[N],q[N],num[N];
int main()
{
    int n,k,ql=1,qr=0,pl=1,pr=0;
    vector<int> ans_min,ans_max;
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;i++){
        scanf("%d",&num[i]);
        while(ql<=qr && q[ql] <= i-k) ql++;//弹出超过范围的最大值
        while(ql<=qr && num[q[qr]] <= num[i]) qr--;//保持单调队列进行插入,若a_i < a_i+1 那么在包含i+1的窗口,a_i永远用不上
        q[++qr] = i;//把索引i 入队
        while(pl<=pr && p[pl] <= i-k) pl++;
        while(pl<=pr && num[p[pr]] >= num[i]) pr--;
        p[++pr] = i;
        if(i>=k){//当索引到k时,第一个窗口出现,每次将队首元素加入相应vector中
            ans_min.push_back(num[p[pl]]);
            ans_max.push_back(num[q[ql]]);
        }
    }
    for(int i=0;i<ans_min.size();i++)
        printf("%d ",ans_min[i]);
    puts("");
    for(int i=0;i<ans_min.size();i++)
        printf("%d ",ans_max[i]);
    puts("");
    return 0;
}

发现了一个神奇的事,把p[N],q[N],num[N]放入到main中的话,程序会爆掉,放到全局变量位置就没事
队列q[ql] q[ql+1] … q[qr] 满足 num[q[ql]] > num[q[qr]] 且 q[ql] < q[qr]
队列p[pl] p[pl+1] … p[pr] 满足 num[p[pl]] < num[p[pr]] 且 p[pl] < p[pr]
队列q的队头是q[ql] 记录的是最大值 队列q的队头是p[pl] 记录的是最小值
无论是对于p还是q
第一步是 将过期了的最大值弹出(即索引在(i-m,i]之外的值)
第二步是 删除队列相应永远不会用到得部分
第三步是 入队
时间复杂度为O(n) 因为每个元素最多入队两次 出队两次
光盘里给的代码 8028K 5563MS

另外对于ST算法与这个算法的区别,ST算法是求数列在下标L–R中最大值,ST算法是一个在线算法,时间复杂度为O(nlogn) 单调队列算法不同的是单调队列是遍历 不需要把值存起来,而ST算法需要保存数组的信息
所以单调队列可以到达O(n)时间复杂度

CH1201 最大子序和

在这里插入图片描述
用前缀和数组,题目求得就是 满足 j − i < = m j - i<=m ji<=m i i i j j j使 m a x ( ∑ 1 ≤ i ≤ j ≤ n s j − s i ) max(\sum_{\newline{1\le i\le j\le n}} s_{j} - s_{i}) max(1ijnsjsi)
固定右边的下标 j j j ( j − m , j ] (j-m,j] (jm,j]的最大,相当于一个窗口里求某个值,不同的窗口对应不同的值,如何从 ( j − m , j ] (j-m,j] (jm,j]的信息来求 ( j − m + 1 , j + 1 ] (j-m+1,j+1] (jm+1,j+1]对应的值,每个窗口都是最右边的值s[j]减去窗口里面的最小值,这就相当于求最小值,那么这里就可以用上一题的方法,代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
using namespace std;

const int N = 300007;
int s[N],q[N];

int main()
{
    int n,m,l=1,r=1,ans = INT_MIN;
    scanf("%d%d",&n,&m);
    s[0] = 0,q[0] = 0;
    for(int i=1;i<=n;i++){
        scanf("%d",&s[i]);
        s[i] += s[i-1];
    }
    for(int i=1;i<=n;i++){
        while(l<=r && q[l] < i-m) l++;
        ans = max(ans,s[i] - s[q[l]]);
        while(l<=r && s[q[r]]>=s[i]) r--;
        q[++r] = i;
    }
    printf("%d\n",ans);
    return 0;
}

注意这里是q[l] < i - m 而上面一题是q[ql] <= i - k,这里不取等号的原因在于

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值