队列练习题

发布时间 2023-12-28 21:25:05作者: Lost-in-love

求m区间内的最小值(洛谷P1440)


题目大意

对一序列a,从左至右扫描,取每个位置前m个数的最小值,位置为首位置时输出0,不足m个数时就取这段范围内的最小值。


解题思路

使用单调队列,保持队头存最小元素下标,从队尾更新最值,超出窗口范围时队头出队。

未知的代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int q[N],a[N],head=1,rear=0,n,m;
int main(){
    scanf("%d %d",&n,&m);	//使用STL容器和cin,cout会超时
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	printf("0\n");
	for(int i=1;i<n;i++){
		while(head<=rear&&a[q[rear]]>a[i])rear--;
		q[++rear]=i;
		while(head<=rear&&q[head]<=i-m)head++;
		printf("%d\n",a[q[head]]);
	}
	return 0;
}

扫描(洛谷P2032)


题目大意

对于序列a,用一个长度为k的窗口从左至右按一单位滑动,求出每次滑动后窗口中的最大值。


解题思路

与上一题同类,换为队头存最大值即可。

未知的代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int a[N],q[N],head=1,tail=0,n,m;
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        while(head<=tail&&a[q[tail]]<a[i])tail--;
        q[++tail]=i;
        if(i>=m){
            while(head<=tail&&q[head]<=i-m)head++;
            printf("%d\n",a[q[head]]);
        }
    }
    return 0;
}

切蛋糕(洛谷P1714)


题目大意

对于序列a,求连续最大个数不超过m的子序列和的最大值,序列值为整数。


解题思路

维护序列前缀和,对前缀和序列使用单调队列求最值。

未知的代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int a,sum[N],n,m,ans=-0x3f3f3f3f;
deque<int>q;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a;
		sum[i]=sum[i-1]+a;
	}
	q.push_back(0); //为求前缀和需要一个初值0
	for(int i=1;i<=n;i++){
		while(!q.empty()&&q.front()<i-m)q.pop_front();
		ans=max(ans,sum[i]-sum[q.front()]);
		while(!q.empty()&&sum[q.back()]>=sum[i])q.pop_back();
		q.push_back(i);
	}
	cout<<ans<<endl;
	return 0;
}

好消息,坏消息(洛谷P2629)


题目大意

对于序列a,可以进行转动,即\(a_1\) \(a_2\) \(\cdots\) \(a_{n-1}\) \(a_n\) -> \(a_k\) \(a_{k+1}\) \(\cdots\) \(a_n\) \(a_1\) \(a_2\) \(\cdots\) \(a_{k-1}\)
求有多少种k使得序列a的前缀和始终不小于0。


解题思路

维护序列前缀和,对前缀和序列使用单调队列求最值。

未知的代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int a[N<<1],s[N<<1],ans[N<<1];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[n+i]=a[i];
		s[i]=s[i-1]+a[i];
	}
	for(int i=n+1;i<=2*n-1;i++)s[i]=s[i-1]+a[i];    //将数组复制一份拼接可计算出题意需要的全部前缀和
	deque<int>q;
	for(int i=1;i<=2*n-1;i++){
		while(!q.empty()&&s[i]<s[q.back()])q.pop_back();
		q.push_back(i);
		while(!q.empty()&&q.front()<i-n)q.pop_front();
		if(i>=n)ans[i]=s[q.front()]-s[i-n]; //注意前缀和的计算
	}
	int cnt=0;
	for(int i=n;i<=2*n-1;i++){
		if(ans[i]>=0)cnt++;
	}
	cout<<cnt<<endl;
	return 0;
}

良好的感觉(洛谷P2422)


题目大意

对于序列a,求出一段子序列中的最小值与子序列元素之和的乘积的最大值。


解题思路

使用单调队列(实为单调栈)维护对应最小值的极大前缀和区间,用以计算最值并更新。

未知的代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int a[N],s[N];
deque<int>q;
signed main()
{
	int n;
	cin>>n;
	for(int i=1; i<=n; ++i){
	    cin>>a[i];
	    s[i]=s[i-1]+a[i];
	}
	int ans = 0;
	for(int i=1; i<=n+1; ++i)
	{
		while(!q.empty() && a[q.back()]>a[i])
		{
			int idx=q.back();
			q.pop_back();
			int left=q.empty()?0:q.back();
		    ans=max(ans,(s[i-1]-s[left])*a[idx]);//保证所更新区间为极大区间
		}
		q.push_back(i);
	}
	cout<<ans<<endl;
	return 0;
}

跳房子(洛谷P3957)


题目大意


解题思路


琪露诺(洛谷P1725)


题目大意


解题思路


小组队列(洛谷P2776)


题目大意


解题思路