单调队列专题1

单调队列专题1

题目来源:洛谷

写在前面:

学习单调队列一周了,许多概念还是似懂非懂。这导致了后面的题目越来越难以进行,对我的态度产生了比较大的消极影响。所以有必要将之前的题目仔细进行剖析,整理,分类。我题目做多了很容易陷入“划水”的状态,就是潜意识里抗拒仔细琢磨题解,潜意识里不想去看正确代码。这导致了我花费更多的时间,但是获得的收益反而比别人更低。接下来的总结分析我会从以下方面开展:

1、问题剖析分类

2、代码实现

3、实现过程的具体数据模拟(包括一般样例和边界样例)

4、和其他问题类型的解法比较

1.P2952 Cow Line S

0、题目大意:

​ 有N头牛要排入一开始为空的队伍。AL是左边排入一头,AR是右边排入一头,DLK是左边离开K头,DRK是右边离开K头。问最后排队情况。

1、问题类型:

​ 简单双端队列模拟

2、代码实现:

#include<bits/stdc++.h>
using namespace std;

int n , k , num ;
char op , x ;
deque<int>q ;

int main(){
	cin>>n ;
	for(int i = 1 ; i <= n ; i ++ ){//n次询问
		cin>>op>>x ;
		if(op == 'A'){
			if(x == 'R'){
				q.push_back(++num) ;//右插入
			}else q.push_front(++num) ;//左插入
		}else{
			cin>>k ;
			while(q.size() && k -- ){
				if(x == 'R')q.pop_back() ;//右出队
				else q.pop_front() ;//左出队
			}
		}
	}
	
	for(auto x : q)cout<<x<<"\n" ;
	
}

3、数据模拟和其他问题比较

​ 这题就是单调队列的基本操作,没什么好说单调。

2、P1886 滑动窗口

0、题目大意:

​ n个数和一个长度为k的窗口,窗口的左端从1开始一位一位向右边滑动,问每一次滑动窗口中最大值和最小值。

1、问题类型:

​ 滑动固定区间最值

2、代码实现:

#include<queue>
#include<bits/stdc++.h>
using namespace std ;
const int N = 1e6+500 ;

int n , k ;
int a[N] ;
deque<int >q ;

int main(){
	
	cin>>n>>k ;
	for(int i = 1 ; i <= n ; i ++ )cin>>a[i] ;
	
	for(int i = 1 ; i <= n ; i ++ ){
		while(q.size() && a[q.back()] >= a[i])q.pop_back() ;
		q.push_back(i) ;//自己也可能是闭区间内最小值
		while(q.size() && i - q.front() >= k)q.pop_front() ;
		if(i >= k)cout<<a[q.front()]<<" "; 
	}//最小值,维护队头到队尾升序的单调队列 
	
	cout<<"\n" ;
	q.clear() ;
	
	for(int i = 1 ; i <= n ; i ++ ){
		while(q.size() && a[q.back()] <= a[i])q.pop_back() ;
		q.push_back(i) ;//自己也可能是闭区间内最大值
		while(q.size() && i - q.front() >= k)q.pop_front() ;//过滤
		if(i >= k)cout<<a[q.front()]<<" "; 
	}//最小值,维护队头到队尾降序的单调队列 
	
}

3、数据模拟:

4、小结:

​ 构造单调队列的时候可以无脑构造,我们取用最值的时候可以根据条件过滤一下(看代码注释)

3、P1638 逛画展

0、题目大意:

​ 有n幅由m个大师画的图画,要找到一个最小的区间,区间中包含所有大师的画。

1、问题类型:

​ 根据条件找区间问题

因为这个区间是有条件的,所以区间长度和该条件可以通过某种关系联系起来,这样就可以用尺取(双指针)的方法解题了。

2、代码实现:

以下是错误代码:

#include<bits/stdc++.h>
using namespace std ;
const int N = 1e6+500 ;

int n , m , num , ansl , ansr = 0x3f3f3f3f;
int a[N] ;
int b[N] ;//一个画家一个桶 

int main(){
	cin>>n>>m ;
	for(int i = 1 ; i<= n ; i ++ )cin>>a[i] ;
	
	int i = 1 , j = 2 ;
	
	num = 1 ;
	b[a[1]] = 1 ;
	
	while(i <= n && j <= n ){//该while错误
		if(!b[a[j]])num++ ;
		b[a[j]]++ ;
		if(num == m){
			if(ansr - ansl > j - i){
				ansr = j ;
				ansl = i ;
			}
			b[a[i]]-- ;
			if(!b[a[i]])num-- ;
			i++ ;
		}
		j ++ ;
	}
	
	cout<<ansl<<" "<<ansr ;
	 
}

错在内循环,每一次循环都将j指针右移。画家数量刚刚好是m时,i指针右移一位之后紧接着下一个循环中j指针就动了。而正解是,在j指针移到画家数量达到m时,i指针不断右移,一直移到画家数量等于m-1为止。然后j指针接着右移去找。正解如下:

#include<bits/stdc++.h>
using namespace std ;
const int N = 1e6+500 ;

int n , m , num , ansl , ansr = 0x3f3f3f3f;
int a[N] ;
int b[N] ;//一个画家一个桶 

int main(){
	cin>>n>>m ;
	for(int i = 1 ; i<= n ; i ++ )cin>>a[i] ;
	
	//限制区间长度的是画家数量m
	//也就是说,只要画家数量到了m就是极限,根据这个来设计双指针程序
	//画家未到m,j右移;画家到m,i右移 
	
	int i = 1 , j = 1 ;
	
	while(i <= n && j <= n + 1){
		if(num == m){
			while(num == m){
				if(ansr - ansl > (j - 1) - i){
					ansr = j - 1;
					ansl = i ;
				}
				b[a[i]] -- ;
				if(!b[a[i]])num -- ;
				i ++ ;
			}
		}else{
			if(!b[a[j]])num ++ ;
			b[a[j]] ++ ;
			j ++ ;//这里j++,就是说如果此时num==m,那j是真正右边界的后一个 
		}
	}
	
	cout<<ansl<<" "<<ansr ;
	 
}

那看到这里,聪明的同学就会问了,这双指针的东西和我们单调队列有什么关系啊?

我们这样想,双指针的左右指针i和j就相当于一个双端队列的front和back。 j不断右移就是队列不断从右入队,i不断右移就是队列不断从左出队。代码如下:

#include<bits/stdc++.h>
using namespace std ;
const int N = 1e6+500 ;

int n , m , num , ansl , ansr = 0x3f3f3f3f;
int a[N] ;
int b[N] ;//一个画家一个桶 
deque<int>q ; 

int main(){
	cin>>n>>m ;
	for(int i = 1 ; i<= n ; i ++ )cin>>a[i] ;
	
	//限制区间长度的是画家数量m
	//也就是说,只要画家数量到了m就是极限,根据这个来设计双指针程序
	//画家未到m,j右移;画家到m,i右移 
    
	int i = 1 ;
	while(i <= n + 1){
		if(num == m){
			while(num == m){
				int idx = q.front() ;
				q.pop_front() ;
				if((i - 1) - idx < ansr - ansl){
					ansr = i - 1 ;
					ansl = idx ;
				}
				b[a[idx]]-- ;
				if(!b[a[idx]])num -- ;
			}
		}else{
			if(!b[a[i]])num ++ ;
			b[a[i]] ++ ;
			q.push_back(i ++ ) ;
		}
	}
	
	cout<<ansl<<" "<<ansr ;
	 
}

P2947 Look Up S

0、题目大意:

​ 每头牛向右边看,输出第一个比他自己高的位置,若没有则出0 。

1、问题类型:

​ 比他大的第一个数

2、代码实现

#include<bits/stdc++.h>
using namespace std ;
const int N = 1e6 + 50 ;
const int INF = 0x3f3f3f3f ;

int n ; 
int a[N] , ans[N];
deque<int>q ;

int main(){
	cin>>n ;
	for(int i = 1 ; i <= n ; i ++ )cin>>a[i] ;
	
	a[0] = INF ;
	q.push_back(0) ;
	for(int i = n ; i > 0 ; i -- ){//向右看倒序遍历 
		while(q.size() && a[i] >= a[q.front()])q.pop_front() ;
		ans[i] = q.front() ;//构造一个栈顶到栈底升序单调栈 
		q.push_front(i) ;
	}
	
	for(int i = 1 ; i <= n ; i ++ )cout<<ans[i]<<"\n";
	
}
posted @ 2021-09-15 20:30  tyrii  阅读(63)  评论(0)    收藏  举报