【笔记】单调栈

单调栈

通常来说,单调栈有3个操作:入栈,出栈,求栈中的最值。可以维护最小值,最大值,或者其它比较函数。三个操作都在O(1)内进行。

实现

维护一个始终保持其中元素单调的栈,如果即将入栈的新元素会破坏单调性,就弹出旧元素,直到新元素不会破坏单调性。

11.1update

板子:求出以每个数为最大/最小值的区域

int n,save[M];
int lmi[M]; //左边第一个小于等于它的位置
int rmi[M]; //右边第一个小于它的位置

int lma[M]; //左边第一个大于等于它的位置
int rma[M]; //右边第一个大于它的位置
void monostack()
{
	stack<int> mi; //单调增栈,存储下标
	stack<int> ma; //单调减栈,存储下标
	mi.push(0);
	ma.push(0);
	for(int i=1;i<=n;++i)
	{
		while( mi.top() && save[mi.top()]>save[i] )
			mi.pop();
		lmi[i] = mi.top();
		mi.push(i);

		while( ma.top() && save[ma.top()]<save[i] )
			ma.pop();
		lma[i] = ma.top();
		ma.push(i);
	}
	while(!mi.empty()) mi.pop();
	while(!ma.empty()) ma.pop();
	mi.push(n+1);
	ma.push(n+1);
	for(int i=n;i>=1;--i)
	{
		while( mi.top()<=n && save[mi.top()]>=save[i] )
			mi.pop();
		rmi[i] = mi.top();
		mi.push(i);

		while( ma.top()<=n && save[ma.top()]<=save[i] )
			ma.pop();
		rma[i] = ma.top();
		ma.push(i);
	}
}

习题

XJTUOJ 1071 单调栈

找不到xjtuoj的朋友可以用下一题来代替。
求权值最大的区间,区间的权值定义为区间长度乘以区间最小值,也即一个直方图中的最大矩形面积。(这道题浪费了我一年的生命)

正反各跑一遍单调栈,记录以每个位置的元素为最小值的最长区间左右端点。
(这也是单调栈最经典的应用)

int save[M],lef[M],rig[M];
int main(void)
{
	int n = read();
	for(int i=1;i<=n;i++)
		save[i] = read();
	stack<int> st; //存放下标
	for(int i=1;i<=n;i++)
	{
		while(!st.empty() && save[st.top()]>save[i])
		{
			rig[st.top()] = i-1;
			st.pop();
		}
		st.push(i);
	}
	while(!st.empty())
	{
		rig[st.top()] = n;
		st.pop();
	}
	for(int i=n;i;i--)
	{
		while(!st.empty() && save[st.top()]>save[i])
		{
			lef[st.top()] = i+1;
			st.pop();
		}
		st.push(i);
	}
	while(!st.empty())
	{
		lef[st.top()] = 1;
		st.pop();
	}
	ll ans = 0;
	for(int i=1;i<=n;i++)
	{
		ans = max(ans,1ll*save[i]*(rig[i]-lef[i]+1));
	}
	printf("%lld\n",ans );

    return 0;
}

POJ 2796 Feel Good

求权值最大的区间,区间的权值定义为区间长度乘以区间和。

/* LittleFall : Hello! */
#include <stack>
#include <iostream>
#include <cstdio>
using namespace std; typedef long long ll;
inline int read(); inline void write(int x);
const int M = 100016, MOD = 1000000007;

int save[M];
int lef[M],rig[M];
ll sum[M];
inline ll cal(int i)
{
	return (sum[rig[i]] - sum[lef[i]-1])*save[i];
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	int n = read();
	for(int i=1;i<=n;i++)
	{
		save[i] = read();
		sum[i] = sum[i-1] + save[i];
	}
	stack<int> st;
	for(int i=1;i<=n;i++)
	{
		while(!st.empty() && save[st.top()]>save[i])
		{
			rig[st.top()] = i-1;
			st.pop();
		}
		st.push(i);
	}
	while(!st.empty())
	{
		rig[st.top()] = n;
		st.pop();
	}
	for(int i=n;i;i--)
	{
		while(!st.empty() && save[st.top()]>save[i])
		{
			lef[st.top()] = i+1;
			st.pop();
		}
		st.push(i);
	}
	while(!st.empty())
	{
		lef[st.top()] = 1;
		st.pop();
	}
	int ans = 0;
	for(int i=1;i<=n;i++)
		if(ans==0 || cal(i) > cal(ans))
			ans = i;
	cout << cal(ans) << endl;
	cout << lef[ans] << " " << rig[ans] << endl;

    return 0;
}


inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
} 

同上题,维护一个前缀和即可。

POJ 3494 Largest Submatrix of All 1’s

求2000*2000的01矩阵中最大的全为1的矩形的面积。

首先O(nm)处理出每个1到它下方最近的1的距离,然后以每行作为子矩形的上边界,问题就转化为求直方图的最大矩形面积。

char save[M][M];
int down[M][M];
int lef[M],rig[M];
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		memset(save,0,sizeof(save));
		memset(down,0,sizeof(down));
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				save[i][j] = read();
		for(int j=1;j<=m;j++) //计算每个1下方有多少个1
			for(int i=1, lst=0; i<=n+1; i++) //lst表示上一个0出现的位置
				if(save[i][j]==0)
				{
					for(int k=lst+1;k<=i-1;k++)
						down[k][j] = i-k;
					lst = i;
				}
		int ans = 0;
		for(int i=1;i<=n;i++)
		{
			stack<int> st; //单调栈
			for(int j=1;j<=m;j++) 
			{
				while(!st.empty() && down[i][st.top()]>down[i][j])	
				{
					rig[st.top()] = j-1;
					st.pop();
				}
				st.push(j);
			}
			while(!st.empty())
			{
				rig[st.top()] = m;
				st.pop();
			}
			for(int j=m;j;j--) 
			{
				while(!st.empty() && down[i][st.top()]>down[i][j])	
				{
					lef[st.top()] = j+1;
					st.pop();
				}
				st.push(j);
			}
			while(!st.empty())
			{
				lef[st.top()] = 1;
				st.pop();
			}
			for(int j=1;j<=m;j++)
			{
				//printf("%d %d %d\n",down[i][j],lef[j],rig[j] );
				ans = max(ans,down[i][j]*(rig[j]-lef[j]+1));
			}
		}
		printf("%d\n",ans );
	}

    return 0;
}

HDU5033 Building (单调栈好题)

有1e5个高度在1e7以内的大楼插在坐标轴上方,1e5个询问,每次问一个地面上的点能看到多少角度的天空。

把询问离线后看成高度为0的楼,跑两次单调栈得出每个楼的顶端向左向右能看到哪些楼。
这道题满足单调栈的性质,定义楼A遮挡楼B为B只能看到A而看不到A之前的所有楼。假设现在要插入楼B,此时栈顶的楼是楼A。
如果楼A可以遮挡楼B,就把B压入栈中。
如果楼A不能遮挡B,那么B之后的楼要么被B遮挡,要么被A之前的楼遮挡,楼A就卵用没有了,弹出楼A,继续判断。

写完这段话,我才明白了单调栈的真实含义。

struct Block
{
	int x;
	int h;
	int q; //查询编号
}block[M];
struct View
{
	int x;
	int l;
	int r;
}view[M];
vector<int> st;
int update(int x,int h)
{
	while(!st.empty() && block[st.back()].h<=h)
		st.pop_back();
	while(st.size()>1)
	{
		int x1 = block[st[st.size()-2]].x;
		int h1 = block[st[st.size()-2]].h;
		int x2 = block[st.back()].x;
		int h2 = block[st.back()].h;
		if(1ll*abs(x-x2)*(h1-h)>=1ll*abs(x-x1)*(h2-h))
			st.pop_back();
		else
			break;
	}
	return st.empty() ? 0 : st.back();
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	int T = read();
	st.reserve(M); //栈
	for(int xT=1;xT<=T;xT++)
	{
		memset(block,0,sizeof(block));
		memset(view,0,sizeof(view));
		int n = read();
		for(int i=1;i<=n;i++)
		{
			block[i].x = read();
			block[i].h = read();
		}
		int q = read();
		for(int i=1;i<=q;i++)
		{
			view[i].x = read();
			block[n+i].x = view[i].x;
			block[n+i].q = i;
		}
		sort(block+1,block+n+q+1,[](const Block&a,const Block&b){
			return a.x<b.x;
		});
		
		st.clear();
		for(int i=1;i<=n+q;i++)
		{
			view[block[i].q].l = update(block[i].x, block[i].h);
			st.push_back(i);
		}
		st.clear();
		for(int i=n+q;i;i--)
		{
			view[block[i].q].r = update(block[i].x, block[i].h);
			st.push_back(i);
		}

		printf("Case #%d:\n",xT );
		for(int i=1;i<=q;i++)
		{
			int xl = block[view[i].l].x;
			int hl = block[view[i].l].h;
			int xr = block[view[i].r].x;
			int hr = block[view[i].r].h;
			int x = view[i].x;
			double ans = atan(1.0*hl/(x-xl)) + atan(1.0*hr/(xr-x));
			printf("%.10f\n",180-ans/3.14159265358979*180 );
		}
	}

    return 0;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值