【POJ-2796】Feel Good【单调栈】

题意:

给出一个序列,长度为 n n n。定义区间价值为区间和*区间最小值,求出这个序列中的最大区间价值。 ( n ≤ 1 0 5 , 0 ≤ a i ≤ 1 0 6 ) (n\leq 10^5,0\leq ai\leq 10^6) (n105,0ai106)


思路:

此题题意十分简洁,求区间价值,并且区间价值定义就是区间和*区间最小值。因此此题最直接的思路就是枚举区间,然后发现 n 2 n^2 n2 算法不可行,然后考虑枚举最小值,对所有的最小值求出这个最小值的区间。

之后就能很自然地想到枚举序列中的每个元素即可,然后问题就变成了对于区间每个数求出这个数字左边第一个比它小的位置,和右边第一个比它小的位置。

于是可以回忆起单调栈正是解决此类问题的经典数据结构。回忆一下单调栈和单调队列,单调栈 —— 对于序列中每个点,求出序列中左/右边第一个比它大/小的点,单调队列 —— 对于序列中每个点,求出序列中距离该点K步范围内的最小/大值。

因此此题最后就变成了单调栈的经典例题,使用单调栈即可完成。

当然此题还可以进行一点改编。如果 a i a_i ai可以为负数,那该如何求解?

做法其实也比较简单,只需要对于每个点 x x x,在其为最小值的 [ l , r ] [l,r] [l,r]区间中找到一段权值最大或最小的区间。因此对 x x x的正负分别进行判断。

假如 x x x为正数,则在 [ x , r ] [x,r] [x,r]区间中找一个前缀和最大的点,再在 [ l − 1 , x − 1 ] [l-1,x-1] [l1,x1]区间中找一个前缀和最小的点,因此形成的这个区间就是权值最大的。

假如 x x x为负数,则在 [ x , r ] [x,r] [x,r]区间中找一个前缀和最小的点,再在 [ l − 1 , x − 1 ] [l-1,x-1] [l1,x1]区间中找一个前缀和最大的点,因此形成的区间是权值最小的。到此,此题结束。


代码:

#include 
#include 
#include 
#include 
#include 
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const int M = 1e5+100;
const db EPS = 1e-9;
using namespace std;

int n,a[N],tt1[N],tt2[N];
ll sum[N];
stack<int> st;

int main()
{
	scanf("%d",&n);
	rep(i,1,n) scanf("%d",&a[i]);
	rep(i,1,n) tt1[i] = 0, tt2[i] = n+1;
	st.push(1);
	rep(i,2,n){
		while(st.size() && a[i] < a[st.top()]){
			int x = st.top();
			st.pop();
			tt2[x] = i;
			// LOG1("x",x);
		}
		if(st.size()) tt1[i] = st.top();
		st.push(i);
	}
	rep(i,1,n) sum[i] = sum[i-1]+(ll)a[i];
	// rep(i,1,n)
	// 	LOG3("i",i,"left",tt1[i],"right",tt2[i]);
	ll ans = 0;
	int l = 1,r = 1;
	rep(i,1,n){
		if(ans < a[i]*(sum[tt2[i]-1]-sum[tt1[i]])){
			ans = a[i]*(sum[tt2[i]-1]-sum[tt1[i]]);
			l = tt1[i]+1;
			r = tt2[i]-1;
		}
	}
	printf("%lld\n",ans);
	printf("%d %d\n",l,r);
	return 0;
}

代码:(进阶问题)

对于每个 a [ i ] a[i] a[i],单调栈找出其为最小值的区间。若 a [ i ] a[i] a[i] 为正数,则在右边找 s u m sum sum 最大,左边找 s u m sum sum 最小。若 a [ i ] a[i] a[i] 为负数,则在右边找 s u m sum sum 最小,左边找 s u m sum sum 最大。

#include 
#include 
#include 
#include 
#include 
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 3*1e6+100;
const int M = 8*1e6+100;
const db EPS = 1e-9;
using namespace std;

int n,tt1[N],tt2[N];
int rt,sz,ls[M],rs[M];
stack<int> st;
ll minn[M],sum[N],a[N],b[N],maxn[M],ans;

void update(int &now,int l,int r,int pos,ll c){
	if(!now) now = ++sz;
	if(l == r){
		minn[now] = maxn[now] = c;
		return;
	}
	int mid = (l+r)>>1;
	if(pos <= mid) update(ls[now],l,mid,pos,c);
	else update(rs[now],mid+1,r,pos,c);
	minn[now] = min(minn[ls[now]],minn[rs[now]]);
	maxn[now] = max(maxn[ls[now]],maxn[rs[now]]);
}

ll query_max(int &now,int l,int r,int lx,int rx){
	if(!now) now = ++sz;
	if(l >= lx && r <= rx){
		return maxn[now];
	}	
	int mid = (l+r)>>1;
	ll ans2 = -1e17;
	if(lx <= mid){
		ll tmp = query_max(ls[now],l,mid,lx,rx);
		ans2 = max(ans2,tmp);
	}	
	if(rx > mid){
		ll tmp = query_max(rs[now],mid+1,r,lx,rx);
		ans2 = max(ans2,tmp);
	}
	return ans2;
}

ll query_min(int &now,int l,int r,int lx,int rx){
	if(!now) now = ++sz;
	if(l >= lx && r <= rx){
		return minn[now];
	}	
	int mid = (l+r)>>1;
	ll ans1 = 1e17;
	if(lx <= mid){
		ll tmp = query_min(ls[now],l,mid,lx,rx);
		ans1 = min(ans1,tmp);
	}	
	if(rx > mid){
		ll tmp = query_min(rs[now],mid+1,r,lx,rx);
		ans1 = min(ans1,tmp);
	}
	return ans1;
}

int main()
{
	ans = -1e17;
	scanf("%d",&n);
	rep(i,1,n) scanf("%lld",&a[i]);
	rep(i,1,n) scanf("%lld",&b[i]);
	rep(i,1,n) sum[i] = sum[i-1]+b[i];
	rep(i,0,n) update(rt,0,n,i,sum[i]);
	rep(i,1,n) tt1[i] = 0, tt2[i] = n+1;
	st.push(1);
	rep(i,2,n){
		while(st.size() && a[i] < a[st.top()]){
			int x = st.top();
			st.pop();
			tt2[x] = i;
		}
		if(st.size()) tt1[i] = st.top();
		st.push(i);
	}
	rep(i,1,n){
		if(a[i] >= 0ll){
			ll tmp = query_max(rt,0,n,i,tt2[i]-1);
			ll tp1 = tmp;
			tmp = query_min(rt,0,n,tt1[i],i-1);
			ll tp2 = tmp;
			ans = max(ans,(tp1-tp2)*a[i]);
		}	
		else{
			ll tmp = query_min(rt,0,n,i,tt2[i]-1);
			ll tp1 = tmp;
			tmp = query_max(rt,0,n,tt1[i],i-1);
			ll tp2 = tmp;
			ans = max(ans,(tp1-tp2)*a[i]);
		}
	}
	printf("%lld\n",ans);
	return 0;
}

你可能感兴趣的:(#,单调栈,数据结构)