zoukankan      html  css  js  c++  java
  • #单调队列 ——滑动窗口 + 扫描(低配滑动窗口)

    第一次

    [点这里到原题](https://www.acwing.com/problem/content/156/)
    
    给定一个大小为n≤106的数组。
    
    有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。
    
    您只能在窗口中看到k个数字。
    
    每次滑动窗口向右移动一个位置。
    
    以下是一个例子:
    
    该数组为[1 3 -1 -3 5 3 6 7],k为3。
    
    窗口位置	最小值	最大值
    [1 3 -1] -3 5 3 6 7	-1	3
    1 [3 -1 -3] 5 3 6 7	-3	3
    1 3 [-1 -3 5] 3 6 7	-3	5
    1 3 -1 [-3 5 3] 6 7	-3	5
    1 3 -1 -3 [5 3 6] 7	3	6
    1 3 -1 -3 5 [3 6 7]	3	7
    您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
    
    输入格式
    输入包含两行。
    
    第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度。
    
    第二行有n个整数,代表数组的具体数值。
    
    同行数据之间用空格隔开。
    
    输出格式
    输出包含两个。
    
    第一行输出,从左至右,每个位置滑动窗口中的最小值。
    
    第二行输出,从左至右,每个位置滑动窗口中的最大值。
    
    输入样例:
    8 3
    1 3 -1 -3 5 3 6 7
    输出样例:
    -1 -3 -3 -3 3 3
    3 3 5 5 6 7
    
    #include<bits/stdc++.h>
    using namespace std;
    int a[1000010];
    deque<int>up;
    deque<int>down;
    int minn[1000010],maxx[1000010];
    int n,k;
    int main()
    {
    	memset(maxx,0,sizeof(maxx));
    	memset(minn,0,sizeof(minn));
    	memset(a,0,sizeof(a));
    	scanf("%d%d",&n,&k);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		while(up.size()&&up.front()<=i-k)//超出范围就出队
    			up.pop_front();
    		while(down.size()&&down.front()<=i-k)//超出范围就出队
    			down.pop_front();
    			
    		if(!up.size())//wrong
    			up.push_back(i);//没东西就进去
    		else{
    			while(up.size()&&a[i]<=a[up.back()])//维护队列单调性
    				up.pop_back();
    			up.push_back(i);
    		}
    		
    		if(!down.size())
    			down.push_back(i);
    		else{
    			while(down.size()&&a[i]>=a[down.back()])
    				down.pop_back();
    			down.push_back(i);
    		}
    		if(i>=k)
    		{
    			maxx[i-k]=a[down.front()];
    			minn[i-k]=a[up.front()];
    		}
    	}
    	for(int i=0;i<=n-k;i++)
    		printf("%d ",minn[i]);
    	printf("
    ");
    	for(int i=0;i<=n-k;i++)
    		printf("%d ",maxx[i]);
    	return 0;
    }
    

    第二次 2020.09.22

    题目

    这里是调试模板,可以看到队列的变化:

    
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1000010;
    int a[N], q[N];
    int hh = 0, tt = -1;
    
    void out(){
    //	for(int i = 0; i < O1; i ++)
    //		printf("%d ", q[i]);	
    //	puts("");
    	
    	printf("当前队列
    ");
    	for(int i = hh; i <= tt; i ++)
    		printf("%d ", a[q[i]] );		
    	puts("");	
    	puts("");
    	
    }
    
    int main()
    {
    	freopen("ttt.in", "r", stdin);
    	freopen("ttt.out", "w", stdout);
    	
        int n, k;
        scanf("%d%d", &n, &k);
        for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    
        int hh = 0, tt = -1;
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt && i - k + 1 > q[hh])
    			hh ++ ;
    			
    			printf("当前队列
    ");
    			for(int i = hh; i <= tt; i ++)
    				printf("%d ", a[q[i]] );		
    			puts("");		
    			puts("");
    		
    			
            while (hh <= tt && a[q[tt]] >= a[i]){
            	tt -- ;
    			printf("当前队列
    ");
    			for(int i = hh; i <= tt; i ++)
    				printf("%d ", a[q[i]] );		
    			puts("");	
    			puts("");
            }
    		
            q[ ++ tt] = i;
    			printf("当前队列
    ");
    			for(int i = hh; i <= tt; i ++)
    				printf("%d ", a[q[i]] );		
    			puts("");	
    			puts("");
    		
            if (i >= k - 1) 
    			printf("________%d 
    
    
    ", a[q[hh]]);
        }
        puts("");
        return 0;
    }
    
    

    答案(第二次)

    #include <iostream>
    using namespace std;
    const int N = 1000010;
    int a[N], q[N];
    int main()
    {
        int n, k;
        scanf("%d%d", &n, &k);
        for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
        
    //这里处理的是最小值
        int hh = 0, tt = -1//头 和 尾
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt    &&   i - k + 1 > q[hh] )  hh ++ ;
            //这里是当队列里面还有元素 以及 头不在这个窗口范围的时候,头就往后走一位
            while (hh <= tt &&   a[q[tt]] >= a[i]  )  tt -- ;
            //这里是要把队列中所有比 现在要插入的元素 大的元素出队
            //然后就会发现 现在队列里的元素 正是当前状态(加入新元素)的单调队列
            q[ ++ tt] = i;
    
            if (i >= k - 1) printf("%d ", a[q[hh]])//队头是最小的
        }
    
        puts("");
        
    //这里处理的是最大值
        hh = 0, tt = -1;
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
    
            while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
            q[ ++ tt] = i;
    
            if (i >= k - 1) printf("%d ", a[q[hh]]);
        }
    
        puts("");
        
        return 0;
    }
    

    第三次 2020.11.15

    第三次刷题地址
    传送到题目
    思路
    经典的单调队列问题,三刷了……
    简单地说就死维护一个单调队列,每次输出队尾即可。

    #include <iostream>
    using namespace std;
    const int N = 1000010;
    int a[N], q[N];
    int main()
    {
        int n, k;
        scanf("%d%d", &n, &k);
        for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
        
    //这里处理的是最小值
        int hh = 0, tt = -1//头 和 尾
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt    &&   i - k + 1 > q[hh] )  hh ++ ;
            //这里是当队列里面还有元素 以及 头不在这个窗口范围的时候,头就往后走一位
            while (hh <= tt &&   a[q[tt]] >= a[i]  )  tt -- ;
            //这里是要把队列中所有比 现在要插入的元素 大的元素出队
            //然后就会发现 现在队列里的元素 正是当前状态(加入新元素)的单调队列
            q[ ++ tt] = i;
    
            if (i >= k - 1) printf("%d ", a[q[hh]])//队头是最小的
        }
    
        puts("");
        
    //这里处理的是最大值
        hh = 0, tt = -1;
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
    
            while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
            q[ ++ tt] = i;
    
            if (i >= k - 1) printf("%d ", a[q[hh]]);
        }
    
        puts("");
        
        return 0;
    }
    

    第四次(这次是用deque做哒!)

    第四刷的博客链接
    四刷了哦,滑动窗口这道题……
    以下是四种定义deque的方法

    // 直接定义
    std::deque<int> first;                                // empty deque of ints
    //填充数字
      std::deque<int> second (4,100);                       // four ints with value 100
      //The contents of second are: 100 100 100 100
    //复制某双端队列的某一段
      std::deque<int> third (second.begin(),second.end());  // iterating through second
      //The contents of third are: 100 100 100 100
    //或者直接复制下整个其它双端队列
      std::deque<int> fourth (third);                       // a copy of third
      //The contents of fourth are: 100 100 100 100
    //或者复制某个数组
      // the iterator constructor can be used to copy arrays:
      int myints[] = {16,2,77,29};
      std::deque<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );
    //The contents of fifth are: 16 2 77 29
    
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e6 + 10;
    int a[N];
    //不要给双端队列q定义大小,我也不清楚
    deque<int>q;
    int main(){
        //个数,窗口大小
        int n, k; cin >> n >> k;
        for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
        //递增
        //依次将数组元素入队
        for(int i = 1; i <= n; i ++){
            //首先是出队操作
            while( !q.empty() && a[i] < a[q.back()]) q.pop_back();
            q.push_back(i);
            if(!q.empty() && q.front() < i - k + 1) q.pop_front();
            if(!q.empty() && i >= k) printf("%d ", a[q.front()]);
        }
        puts("");
        //递减
        q.clear();
        for(int i = 1; i <= n; i ++){
            //首先是出队操作
            while( !q.empty() && a[i] > a[q.back()]) q.pop_back();
            q.push_back(i);
            if(!q.empty() && q.front() < i - k + 1) q.pop_front();
            if(!q.empty() && i >= k) printf("%d ", a[q.front()]);
        }
        puts("");
        
        return 0;
    }
    
    

    第五次

    这次是在周测10做的,只用到单调递减队列。
    题目 : 扫描

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 3e6 + 10;
    deque<int>q;
    int a[N];
    int main(){
    	int n, k;
    	cin >> n >> k;
    	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    	for(int i = 1; i <= n; i ++){
    		//首先当然是维护队列的单调性啦! 
    		while(!q.empty() && a[i] > a[q.back()]) q.pop_back();
    		if(!q.empty() && q.front() < i - k + 1) q.pop_front();
    		q.push_back(i);
    		if(i >= k) printf("%d
    ", a[q.front()] );
    	}
    	return 0;
    }
    
  • 相关阅读:
    P3469 [POI2008]BLO-Blockade
    洛谷P2342 叠积木
    洛谷 P1197 [JSOI2008]星球大战
    洛谷P1967 货车运输
    洛谷P2812校园网络【Network of Schools加强版】
    洛谷P3003 苹果交货Apple Delivery
    luogu Eat the Trees
    插头DP模板
    [NOIP2017] 宝藏
    LOJ6268拆分数
  • 原文地址:https://www.cnblogs.com/yuanyulin/p/14026797.html
Copyright © 2011-2022 走看看