单调队列专题总结

1、HDU - 4193

题意:

          n个数组成的序列环,求长度小于等于m的字串的最大区间和。

思路:

          如果n个数为线性关系,可以用单调队列维护第i个数之前的m个数,

及时剔除没用的数。o(N)解决。

这里是环,2倍数组破环成链即可。(代码非自己写)

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define maxn 1000005
int n,m,head,rear,ans;
int a[maxn*2],q[maxn*2];
void in_queue(int x)
{
	while(head<=rear && a[x]<=a[q[rear]])//删除队中大于等于它的元素
		rear--;
	q[++rear]=x;//满足条件则下标入队
}
void out_queue(int x)
{
	if(q[head]<x-n+1)
		head++;
}
int  main()
{
	int i;
	while(scanf("%d",&n),n!=0)
	{
		head=ans=0;
		rear=-1;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			a[n+i]=a[i];
		}
		for(i=2;i<2*n;i++)
			a[i]+=a[i-1];
		for(i=1;i<n;i++)
			in_queue(i);
		for(i=n;i<2*n;i++) //依次求区间[i-n+1,i]中的最小值 
		{
			in_queue(i);
			out_queue(i);
			if(a[q[head]]-a[i-n]>=0)//如果最小值都大于0,计数
				ans++;
		}
		printf("%d\n",ans);
	}
}

2、HDU - 3474

题意:

        由字母C和J组成的序列环,可以从任意一个地方断开,

要求断开后向右或者向左便利序列的过程中保持Number C>=J,

求满足要求的切割方案数。

思路:

暴力:破环成链,枚举终点(或者中点起点都可以),看终点往前的n个数是否满足要求。O(N*N);

思考:

      n个数满足要求即n个数所有以第一个数为起点的字串和均>=0,或者说其中

最小前缀和-第一个数-1的前缀和>=0,所以问题变为求以第i个数为起点,长度最

长为n的的字串中最小 的前缀和。

     也可以转换为求以第i个数为末尾,长度不超过n的字串中最小的前缀和。只要

起点到该点的序列和>=0 ,则段序列对应的切割点为可行方案。(PS:最开使看的

模板题也是求以第i个数为终点,长度不超过m的字串中最小的/最大的前缀和,(

用单调队列来维护一个长度小于m的序列,该序列只在队头和队尾修改)所以这

题其实是模板的一个变式,如果直接从原理上思考的话不太好考虑)。

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=2e6+50;
int n,m,k;
int tail,head;
int stk[maxn];
int ans;
int a[maxn];
int c[maxn];
char s[maxn];
bool jd[maxn];
int main()
{
    int T,cas=1;
    scanf("%d",&T);
    getchar();
    while(T--)
    {
        memset(jd,0,sizeof(jd));
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n;i++)
        {
            a[i]=(s[i]=='C')?1:-1;
            a[n+i]=a[i];
        }
        c[0]=0;
        for(int i=1;i<=2*n;i++)
        {
            c[i]=c[i-1]+a[i];
        }
        ans=0;
        head=-1;tail=0;
        for(int i=1;i<2*n;i++)
        {
            while(head>=tail&&c[i]<c[stk[head]]) head--;
            stk[++head]=i;
            while(head>tail&&i-stk[tail]>=n) tail++;
            if(i>=n&&c[stk[tail]]-c[i-n]>=0) jd[i-n+1]=1;
        }
        c[0]=0;
        for(int i=1;i<=2*n;i++)
        {
            int k=n+n-i+1;
            c[i]=c[i-1]+a[k];
        }
        head=-1;tail=0;
        for(int i=1;i<=2*n;i++)// i<=2*n
        {
            while(head>=tail&&c[i]<c[stk[head]]) head--;
            stk[++head]=i;
            while(head>tail&&i-stk[tail]>=n) tail++;
            if(i>n&&c[stk[tail]]-c[i-n]>=0) jd[n-(i-n)+1]=1;//i>n 注意正反切割地点的下标要一致
        }
        for(int i=1;i<=n;i++)
        if(jd[i]) ans++;
        printf("Case %d: %d\n",cas++,ans);
    }
    return 0;
}

3、HDU - 3530

题意:n个数,求最长 字串 的长度,且该字串满足m<=maxs-mini<=k,,N<=1e5。

思路: 

暴力:枚举所有子串,计算最大值最小值,O(N*N*N)/,虽然线段树不会,不过线段树优化的话也是O(N*N*logN)。

        本质是求子串的最值,所以目前来说有O(N)的单调队列解法。所以考虑开两个队列,

一个递增、一个递减,两者取队头坐标小者作为左边界,i是右边界。当maxs-mini小于m时

进入下一个i,大于k时,调整队列头指针位置,调整谁的呢,就问一句:调整index大的有

用么,调整后原来的对头不照样被区间包含。然后更新ans即可,ans=max(ans,i-L+1+1)

(理想状态下len=不合法的len-1,不行的时候len'不再是len-2,要根据此时的L确定)。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;

const int MAXN=100010;
int q1[MAXN],q2[MAXN];
int rear1,head1;
int rear2,head2;
int a[MAXN];
int main()
{
  //  freopen("in.txt","r",stdin);
  //  freopen("out.txt","w",stdout);
    int n,m,k;

    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        rear1=head1=0;
        rear2=head2=0;
        int ans=0;
        int now=1;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            while(head1<rear1&&a[q1[rear1-1]]<a[i])rear1--;//这里的等号取和不取都可以的
            while(head2<rear2&&a[q2[rear2-1]]>a[i])rear2--;
            q1[rear1++]=i;
            q2[rear2++]=i;
            while(head1<rear1&&head2<rear2&&a[q1[head1]]-a[q2[head2]]>k)
            {
                if(q1[head1]<q2[head2])now=q1[head1++]+1;
                else now=q2[head2++]+1;
            }
            if(head1<rear1&&head2<rear2&&a[q1[head1]]-a[q2[head2]]>=m)
            {
                //int t=min(q1[head1],q2[head2]);
                if(ans<i-now+1)ans=i-now+1;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

4、HDU-4122(二维优先队列)

题意:

n分订单,只在m个时刻做蛋糕(第一个时刻:0~1hour);

n份订单要提交的时刻,每份订单所需要蛋糕的数目;

每个蛋糕自生产时刻起能够保存几个小时,保存一个小时所需要的花费;

m个生产时刻生产蛋糕时生产一个所需要的花费;

N<=2500,M<=1e5,,,

自己的思路:

         O(M)遍历M序列,所需维护的元素最多T个,且维护过程的增删为“窗口滑动”型,

所以队列维族该集合,具体维护以  i 为终点,长度不超过T的字串中花费的最小值,显然

若在一个时刻k后有比其花费小的时刻j,那j之后所有的 时刻中k时刻都不会作为最小花费

被选择,k为无用信息,去掉后即为单调增队列数据结构。(直接单调队列维护)。最后

遍历所有订单提交时刻,统计花费总数。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
const int MAX=2500+10;
char mon[5];
int M[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
struct Node{
	int num,t;
//对于订单来说num表示月饼数量,t表示订单时间;
//对于制作月饼来说num表示耗费的价值,t表示制作时间 
}order[MAX],q[MAX*4];//q表示单调递增队列
int MON(char *m){
	if(strcmp(m,"Jan") == 0)return 1;
	if(strcmp(m,"Feb") == 0)return 2;
	if(strcmp(m,"Mar") == 0)return 3;
	if(strcmp(m,"Apr") == 0)return 4;
	if(strcmp(m,"May") == 0)return 5;
	if(strcmp(m,"Jun") == 0)return 6;
	if(strcmp(m,"Jul") == 0)return 7;
	if(strcmp(m,"Aug") == 0)return 8;
	if(strcmp(m,"Sep") == 0)return 9;
	if(strcmp(m,"Oct") == 0)return 10;
	if(strcmp(m,"Nov") == 0)return 11;
	return 12;
}
bool LeapYear(int &year){
	return year%4 == 0 && year%100 || year%400 == 0;
}
 int Time(int &year,int Mon,int &d,int &h){
	int t=0;
	for(int i=2000;i<year;++i){
		if(LeapYear(i))t+=366;
		else t+=365;
	}
	bool flag=LeapYear(year);
	for(int i=1;i<Mon;++i){
		if(flag && i == 2)t+=29;
		else t+=M[i];
	}
	t+=d-1;
	return t*24+h;
}
int main(){
	int n,m,t,s,r,h,d,year,a;
	while(~scanf("%d%d",&n,&m),n+m){
		for(int i=0;i<n;++i){
			scanf("%s%d%d%d%d",mon,&d,&year,&h,&r);
			order[i].num=r,order[i].t=Time(year,MON(mon),d,h);
//转化成小时 
		}
		int top=0,tail=0,p=0;
		__int64 sum=0;
		scanf("%d%d",&t,&s);
		for(int i=0;i<m;++i){
			scanf("%d",&a);
			while(top<tail && q[tail-1].num+(i-q[tail-1].t)*s>=a)--tail;
			q[tail].num=a,q[tail++].t=i;
			while(p<n && i == order[p].t){
				while(q[top].t+t<i)++top;
				sum+=(q[top].num+(i-q[top].t)*s)*order[p++].num;
			}
		}
		printf("%I64d\n",sum);
	}
	return 0;
} 

 

5、FZU- Problem 2080

题意:给定一个n*m的矩阵,从其中所有c*r子矩阵的最大值-最小值中选一个最大的作为结果。

思路:

         首先,对于这种统计最值的问题可以用遍历所有元素来解决,但如果是求字串的最值,

遍历的话需要o(n*n)的时间复杂度,所以我们引入了单调队列,其实质就是求一段

序列各种字串的最值。(单调队列最常解决的问题)

        对应到矩阵上来,求所有子矩阵的最值,仍然可以采取遍历的方式,但是时间复杂度

O(N*M*C*R) ,然后另一种求矩阵最值的方案,先求出每列的最值,然后取其中最值就是整

个矩阵的最值(要从整个矩阵上考虑,枚举每一个子阵的话显然更慢),仍然按照朴素算

法来算的话,时间复杂度O(M列*N起点*Len长度*N遍历)+O(N行*M起点*len长度*M遍历)

(该题中Len为固定值,可以忽略),1second肯定TLE了,考虑这实际上就是在求序列字串

的最值,属于单调队列的应用之一,所以对整个矩阵操作,时间复杂度为O(M*N)+O(N*M)

,,,完美。

#include<iostream>
#include<cstdio>
#include<map>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;
#define N 1005
int head,tail,mat[N][N],vmax[N][N],vmin[N][N];         
 //vmax[i][j]表示(i,j)到(i+d-1,j+d-1)中的最大值,
//vmin[i][j]表示(i,j)到(i+d-1,j+d-1)中的最小值。
struct node
{
    int num,id;
};
node que[N];
void add_max(int num,int id)
{
    while(head<tail&&que[tail-1].num<=num)tail--;      //单调队列维护
    que[tail].id=id;
    que[tail++].num=num;
}
void add_min(int num,int id)
{
    while(head<tail&&que[tail-1].num>=num)tail--;
    que[tail].id=id;
    que[tail++].num=num;
}
int get_num(int id)
{
    while(head<tail&&que[head].id<id)head++;
    return que[head].num;
}
int main()
{
    //freopen("a.txt","r",stdin);
    int n,m,r,c;
    while(scanf("%d%d%d%d",&n,&m,&r,&c)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                scanf("%d",&mat[i][j]);
            }
        }
        for(int i=0;i<m;i++)
        {
            head=tail=0;
            for(int j=0;j<r-1;j++)add_max(mat[j][i],j);
            for(int j=r-1;j<n;j++)
            {
                add_max(mat[j][i],j);
                vmax[j-r+1][i]=get_num(j-r+1);          
        //这时的vmax[j-r+1][i]表示第i列中的第j-r+1行到第j行的最大值
            }
        }
        for(int i=0;i<=n-r;i++)
        {
            head=tail=0;
            for(int j=0;j<c-1;j++)add_max(vmax[i][j],j);         
     //这里就是二维单调队列维护了
            for(int j=c-1;j<m;j++)
            {
                add_max(vmax[i][j],j);
                vmax[i][j-c+1]=get_num(j-c+1);
            }
        }
        for(int i=0;i<m;i++)
        {
            head=tail=0;
            for(int j=0;j<r-1;j++)add_min(mat[j][i],j);
            for(int j=r-1;j<n;j++)
            {
                add_min(mat[j][i],j);
                vmin[j-r+1][i]=get_num(j-r+1);
            }
        }
        for(int i=0;i<=n-r;i++)
        {
            head=tail=0;
            for(int j=0;j<c-1;j++)add_min(vmin[i][j],j);
            for(int j=c-1;j<m;j++)
            {
                add_min(vmin[i][j],j);
                vmin[i][j-c+1]=get_num(j-c+1);
            }
        }
        int ans=0;
        for(int i=0;i<=n-r;i++)
        {
            for(int j=0;j<=m-c;j++)
            {
                ans=max(ans,vmax[i][j]-vmin[i][j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

The end;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值