1. 程式人生 > >【演算法詳解】對於單調棧的重新理解

【演算法詳解】對於單調棧的重新理解

對於單調棧的重新理解

關於什麼是單調棧和為什麼要用單調棧:

單調棧,就是棧中的元素始終是單調遞增的。
對於某類特殊的問題,若當前狀態與下一狀態的單調性有關,就必須使用單調棧。

亂頭髮節

題目描述
賢正的某 N 頭奶牛 (1 <= N <= 80,000) 正在過亂頭髮節!由於每頭牛都 意識到自己凌亂不堪的髮型, 寧智賢希望統計出能夠看到其他牛的頭髮的牛的數量。
每一頭牛 i有一個高度 h[i] (1 <= h[i] <= 1,000,000,000)而且面向東方排成 一排(在我們的圖中是向右)。因此,第i頭牛可以看到她前面的那些牛的頭, (即i+1, i+2,等等),只要那些牛的高度嚴格小於她的高度。
例如這個例子:
亂頭髮節 圖


牛#1 可以看到她們的髮型 #2, 3, 4 牛#2 不能看到任何牛的髮型 牛#3 可以看到她的髮型 #4 牛#4 不能看到任何牛的髮型 牛#5 可以看到她的髮型 6 牛#6 不能看到任何牛的髮型!
讓 c[i] 表示第i頭牛可以看到髮型的牛的數量;請輸出 c[1] 至 c[N]的和。 如上面的這個例子,正確解是3 + 0 + 1 + 0 + 1 + 0 = 5。
輸入格式
Line 1: 牛的數量 N。
Lines 2…N+1: 第 i+1 是一個整數,表示第i頭牛的高度。
輸出格式
Line 1: 一個整數表示c[1] 至 c[N]的和。
樣例資料
input

6
10
3
7
4
12
2
output

5

對於這道題目,發現當一隻新的奶牛進來後,比原來的奶牛要高,那麼這隻原來的奶牛無論如何也看不到什麼牛,就屬於無用狀態,顯然只有比新加入的奶牛高才能繼續發揮統計答案的作用。因此,我們可以維護一個單調遞減的序列:
當新加入的奶牛比棧頂的奶牛矮,答案加上top(表示原來的奶牛都看得到)
當新加入的奶牛比棧頂的奶牛高,把所有比它矮的奶牛從棧中彈出(改狀態無用),答案加top(表示剩下的奶牛都能看得清這一隻奶牛),並進棧。
演算法原理理解後,就很好實現了。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,
a[100000],st[100000],top=0; long long ans=0; cin>>n; for (int i=1;i<=n;i++)cin>>a[i]; for (int i=1;i<=n;i++) { while (a[i]>=st[top]&&top>0) top--; ans+=top; st[++top]=a[i]; } cout<<ans; return 0; }

地平線

題目描述
  Farmer John的牛們認為,太陽升起的那一刻是一天中最美好的,在那時她們 可以看到遠方城市模糊的輪廓。顯然,這些輪廓其實是城市裡建築物模糊的影子。   建築物的影子實在太模糊了,牛們只好把它們近似地看成若干個邊長為1單位 長度的正方體整齊地疊在一起。城市中的所有建築物的影子都是標準的矩形。牛們 的視野寬W個單位長度(1<=W<=1,000,000),不妨把它們按從左到右劃分成W列,並 按1~W編號。建築物的輪廓用N組(1<=N<=50,000)數給予描述,每組數包含2個整數 x、y(1<=x<=W,0<=y<=500,000),表示從第x列開始,建築物影子的高度變成了y。 (也就是說,第x[i]列到第x[i+1]-1列中每一列建築物影子的高度都是y[i]個單位 長度)
  貝茜想知道這座城市裡最少有多少幢建築物,也就是說,這些影子最少可以由 多少個矩形完全覆蓋。當然,建築物的影子可以有重疊。請你寫一個程式幫她計算 一下。
題目描述2
地平線 圖
對於這道題,我們可以這麼想:
對於擺放的每一塊積木,如果高度要比上一個矩陣高,那麼必然多出一塊矩陣;
如果高度比上一個矩陣低,必然上一個矩陣要多出來;如果高度相等,則當前高度可以補到前面的高度上面去。
因此,維護一個單調遞增的棧:
1.比棧頂高,進棧。
2.比棧頂低,彈出比它高的矩陣,累加答案;繼續進棧。
3.和棧頂一樣高,什麼都不做。

#include<bits/stdc++.h>
using namespace std;
int n,w,k,ans=0,a[600000]={},top=0,st[600000]={};
int main()
{
	cin>>n>>w;
	for (int i=1;i<=n;i++) cin>>k>>a[i];
	if (n==1&&a[1]==0)
	{
		cout<<0;
		return 0;
	}
	for (int i=1;i<=n;i++)
	{
		while (a[i]<st[top]&&top>0) 
		{
		    ans++;
		    top--;
		}//如果小,則把比它大的元素彈出 
		if (a[i]>st[top]) st[++top]=a[i];
	}
	cout<<ans+top;
	return 0;
}

Largest Rectangle in a Histogram

英文題面這道題的解體思路和地平線大致相同:
對於每一個矩陣,如果下一個比它高,凸出的部分必然沒有;因此我們只需要使用單調棧維護單調遞增,再用另外一個數組維護這個方塊的高度往左最多能夠放下的寬度即可。

#include<bits/stdc++.h>
using namespace std;
#define maxn 100100
#define qword long long

int n;
int s[maxn];
int a[maxn];
int w[maxn];

inline void read(int &readnum)
{
	int s=0,w=1;char c=getchar();
	while (c<'0' || c>'9') {if (c=='-') w=-1;c=getchar();}
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s*w;
}

inline void Mem(void)
{
	memset(a,0,sizeof(a));
	memset(w,0,sizeof(w));
	memset(s,0,sizeof(s));
}

void work(void)
{
	for (int i=1;i<=n;++i) read(a[i]);
	int t=0;
	qword ans=0;
	for (int i=1;i<=n+1;++i)
	{
		if (a[i]>=s[t]) w[++t]=1,s[t]=a[i];
		//高於棧頂,進棧 
		if (a[i]<s[t])
		{
			int sum=0;
			while (a[i]<s[t])
			//低於棧頂,出棧並統計方案數 
			{
				sum+=w[t];
				//累加當前矩陣的高度所能放的最大寬度 
				ans=max(ans,(qword)sum*s[t]);
				//用高度乘上寬度統計答案 
				t--;
				//彈出 
			}
			s[++t]=a[i],w[t]=sum+1;
			//進棧,標記當前高度最大能往左放幾個格子 
		}
	}
	printf("%lld\n",ans);
}

int main(void)
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	read(n);
	while (n)
	{
		Mem();
		work();
		read(n);
	}
	return 0;
}