zoukankan      html  css  js  c++  java
  • 浅谈单调栈/队列

    Preface

    其实我是真的不想写这个东西的,只不过做了一些这方面的水题,干脆写成一个专题

    真的是水题,不毒瘤的PJ难度水题。我真是太菜了


    思想简介

    在维护一段区间的最值时,你一般会怎么做?

    (O(1))查询RMQ,或者是什么都能搞的线段树

    如果只需要求一次呢?

    还是RMQ/线段树

    如果数据范围为(10^7) or more?

    我们就可以用到玄学的单调栈/队列了。

    首先相信栈和队列大家都不陌生吧,那么加了个单调上去意味着什么呢?

    序列是单调的(废话),说明我们可以很快的找出最值

    好像有这么一点道理,例如我们要求出一段序列的最小值

    我们使一个队列里的元素单调递减,具体的:

    对于每一个元素(xin q),都有(q_x>q_{x+1})

    因此我们直接把队头拿出来就好了啊。

    那么可能有人要问了,这TM不是直接开一个变量记录一下最小值就好的事情吗?

    但是询问区间你左端点还是要向右弹出的啊!因此我们都要保留一个对未来可能有用的解。

    只有当一对(x,y)元素满足(x>y)(a_x<a_y)(y)的存在才没有任何有意义。

    复杂度是比较卓越的(O(n)),一般起辅助作用,常用来优化DP等算法。

    不过我们今天不讲这么难的,我们只讲SB板子题


    POJ 2823 Sliding Window

    题目大意:求一段长度为(k)的区间内的最小/大值。

    这个单调队列板子题。上面已经讲的比较详细了,我们维护两个单调队列即可。

    CODE

    #include<cstdio>
    #include<cctype>
    const int N=1e6+5;
    using namespace std;
    int n,k,x;
    inline char tc(void)
    {
    	static char fl[100000],*A=fl,*B=fl;
    	return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
    }
    inline void read(int &x)
    {
    	x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    	while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag;
    }
    inline void write(int x)
    {
    	if (x<0) putchar('-'),x=-x;
    	if (x>9) write(x/10); putchar(x%10+'0');
    }
    namespace min
    {
    	int q[N],num[N],ans[N],head=1,tail,cnt;
    	inline void push(int x,int id)
    	{
    		while (tail>=head&&q[tail]>x) --tail;
    		q[++tail]=x; num[tail]=id;
    	}
    	inline void check(int now)
    	{
    		if (num[head]+k<=now) ++head;
    		ans[++cnt]=q[head];
    	}
    };
    namespace max
    {
    	int q[N],num[N],ans[N],head=1,tail,cnt;
    	inline void push(int x,int id)
    	{
    		while (tail>=head&&q[tail]<x) --tail;
    		q[++tail]=x; num[tail]=id;
    	}
    	inline void check(int now)
    	{
    		if (num[head]+k<=now) ++head;
    		ans[++cnt]=q[head];
    	}
    };
    int main()
    {
    	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    	register int i; read(n); read(k);
    	for (i=1;i<=k;++i)
    	read(x),min::push(x,i),max::push(x,i);
    	min::check(k); max::check(k);
    	for (i=k+1;i<=n;++i)
    	read(x),min::push(x,i),max::push(x,i),min::check(i),max::check(i);
    	for (i=1;i<=min::cnt;++i)
    	write(min::ans[i]),putchar(i^min::cnt?' ':'
    ');
    	for (i=1;i<=max::cnt;++i)
    	write(max::ans[i]),putchar(i^max::cnt?' ':'
    ');
    	return 0;
    }
    

    Luogu P2422 良好的感觉

    比较简单的套路题,拿来练一下RMQ也是挺好的。

    我们对于每一个点,处理出它两端大于它的第一个位置的数

    这样我们就可以算出当(iin [l,r],a_i=min(l,r))时每一个数的贡献。

    这样保证不会遗漏。由于(a_i>0)。因此取的数越多越好

    CODE

    #include<cstdio>
    #include<cctype>
    const int N=1e5+5;
    using namespace std;
    int n,k,x,a[N],stack[N],front[N],back[N],top,num[N];
    long long sum[N],ans;
    inline char tc(void)
    {
    	static char fl[100000],*A=fl,*B=fl;
    	return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
    }
    inline void read(int &x)
    {
    	x=0; char ch; while (!isdigit(ch=tc()));
    	while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
    }
    inline long long max(long long a,long long b)
    {
    	return a>b?a:b;
    }
    int main()
    {
    	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    	register int i; read(n);
    	for (i=1;i<=n;++i)
    	read(a[i]),sum[i]=sum[i-1]+a[i];
    	for (i=1;i<=n;++i)
    	{
    		while (top&&stack[top]>=a[i]) --top;
    		front[i]=num[top]; stack[++top]=a[i]; num[top]=i;
    	}
    	for (top=0,num[0]=n+1,i=n;i>=1;--i)
    	{
    		while (top&&stack[top]>=a[i]) --top;
    		back[i]=num[top]; stack[++top]=a[i]; num[top]=i;
    	}
    	for (i=1;i<=n;++i)
    	ans=max(ans,1LL*a[i]*(sum[back[i]-1]-sum[front[i]]));
    	return printf("%lld",ans),0;
    }
    

    Luogu P2629 好消息,坏消息

    又是一道SB题,我们先将序列展开,方便操作。

    (iin[n+1,2n-1],a_i=a_{i-n}),然后计算出前缀和

    考虑每一次倒叙,就相当于求出一段长为(n)的区间的前缀和的最小值,并判断是否小于(0)

    然后区间大小都固定了,上单调队列不是很爽吗?

    CODE

    #include<cstdio>
    #include<cctype>
    const int N=1e6+5;
    using namespace std;
    int n,k,x,a[N],q[N<<1],head=1,tail,num[N<<1],sum[N<<1],ans;
    inline char tc(void)
    {
    	static char fl[100000],*A=fl,*B=fl;
    	return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
    }
    inline void read(int &x)
    {
    	x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    	while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag;
    }
    inline void push(int x,int id)
    {
    	while (tail>=head&&q[tail]>x) --tail;
    	q[++tail]=x; num[tail]=id;
    }
    inline int check(int now)
    {
    	if (num[head]>now) ++head;
    	return q[head];
    }
    int main()
    {
    	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    	register int i; read(n);
    	for (i=1;i<=n;++i)
    	read(a[i]),sum[i]=sum[i-1]+a[i];
    	for (i=n+1;i<(n<<1);++i)
    	sum[i]=sum[i-1]+a[i-n];
    	for (i=(n<<1)-1;i>=n;--i)
    	push(sum[i],i);
    	for (i=n-1;i>=0;--i)
    	{
    		if (check(i+n)>=sum[i]) ++ans;
    		push(sum[i],i);
    	}
    	return printf("%d",ans),0;
    }
    
  • 相关阅读:
    USACO 1.2 Broken Necklace
    USACO 1.2 Friday the Thirteenth
    USACO 1.1 Greedy Gift Givers
    USACO 1.1 Your Ride Is Here
    CSP考试策略
    CF444A DZY Loves Physics【结论】
    树状数组-复习笔记
    CF792E Colored Balls【思维】
    USACO4.4 Shuttle Puzzle【bfs+优化】
    拓扑排序-学习笔记
  • 原文地址:https://www.cnblogs.com/cjjsb/p/9416168.html
Copyright © 2011-2022 走看看