算法与数据结构实验题 3.11 happiness

实验任务

这一天是小V 的生日,他收到了朋友们送给他的礼物。

现在,小V 有n 件礼物,他将这n 件礼物排成一排,依次编号为1 到n,每件礼物都有一个满意值w[i]。

现在小V 要从中选取连续编号的礼物(即选取[l, r]内的礼物),使得获得的 happiness 最大。

[l, r]内的 happiness 定义为:
([l,r]内所有礼物满意值的最小值)×([l,r]内所有礼物满意值的和)
小V 想知道他能获得的 happiness 最大是多少,你能帮帮他吗?

数据输入

第一行为一个正整数 n。

第二行为n 个整数 w[1], w[2], …, w[n]

其中:

对于 50%的数据:1<=n<=100, 0<=w[i]<=100

对于 80%的数据:1<=n<=1,000, 0<=w[i]<=1,000

对于 100%的数据:1<=n<=100,000, 0<=w[i]<=10,000

数据输出

小 V能获得的最大 happiness值。

输入示例1

3
1 2 3

输出示例1

10

输入示例2

3
2 1 3

输出示例2

9

代码一

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=2e9+1;

int n,a[100010],l[100010],r[100010];
ll ans,sum[100010];
stack<int>stkl,stkr;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	for(int i=1;i<=n;i++) {
		while(!stkl.empty()&&a[stkl.top()]>=a[i]) stkl.pop();
		if(!stkl.empty()) l[i]=stkl.top()+1;
		else l[i]=1;
		stkl.push(i);
	}
	for(int i=n;i>=1;i--) {
		while(!stkr.empty()&&a[stkr.top()]>=a[i]) stkr.pop();
		if(!stkr.empty()) r[i]=stkr.top()-1;
		else r[i]=n;
		stkr.push(i);
	}
	for(int i=1;i<=n;i++) ans=max(ans,a[i]*(sum[r[i]]-sum[l[i]-1]));
	printf("%lld",ans);
	return 0;
}

代码二 · 代码一单栈精简版

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=2e9+1;

int n,i;
stack<ll>s;
ll ans,a[100010],sum[100010];

int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++) {
		scanf("%lld",&a[i]);
		if(i) sum[i]=sum[i-1]+a[i];
		else sum[i]=a[i];
	}
	while(i<=n) {
		if(s.empty()||a[s.top()]<=a[i]) s.push(i++);
		else {
			int t=s.top();
			s.pop();
			if(s.empty()) ans=max(ans,sum[i-1]*a[t]);
			else ans=max(ans,(sum[i-1]-sum[s.top()])*a[t]);
		}
	}
	printf("%lld",ans);
	return 0;
}

思路
本题考点为单调栈,应该可以说是这一套课程下来最难的题了

代码一的总体思路讲解如下,代码二即为代码一的简化版,理解难度较大,这里暂不讲解

本体考查一个区间内的最小值和数值之和,数值和自然不用多说,用前缀和解决即可,现在的问题在于,区间最小值该如何实现?

我们从一个例子来入手:

7
1 2 3 5 4 1 2

假设我们用数组 \(l\) 来表示区间的开始位置,用数组 \(r\) 来表示区间的结束位置,那么接下来的问题就是,怎么填充 \(l\)\(r\) 数组,来使得 \(l\) \(r\) 区间取得 \(最小值 \times 总和\) 的最大值;换个角度说,就是怎么求出 \(a[i]\) 所对应的 \(l[i]\)\(r[i]\) ,使得 \(a[i]\) 在这个范围内是最小值。

用最简单暴力的思路想,就是对于每一个 \(a[i]\) ,我们都暴力往后查找,直到找到比他小的数为止,但很显然这是必然会超时的,但总体思路来说是正确的。

思考一下,当我们取得最大值时,如果第1个“1”在我们所考虑的区间之内,那么第6个“1”也必然在,因此囊括在两个“1”区间的元素在最小值不变的情况下,取得了总和的最大值,这就是一个区间内的最优解,那我们要怎么去向这个最优解靠拢呢?

我们翻译一下刚刚思考的过程,如果 \(l\) 此时取 6 ,那么我们就应该把他改成 1 ,以此来取得区间的最优开始点,其中需要保持两个“1”之间没有比 1 更小的数,因此此时生成的 \(l\) 数组,就是从 \(l[i]\) 开始时,区间内的最小值为 \(a[i]\)

所以, \(l\) 数组所代表的,就是你从 \(i\) 开始,不如从 \(l[i]\) 开始,此时区间内的最小值为 \(a[i]\);同理, \(r\) 数组所代表的,就是你以 \(i\) 结束,不如以 \(r[i]\) 结束,此时区间内的最小值为 \(a[i]\);二者结合,意思就是在 \(l[i]\)\(r[i]\) 之间的最小值为 \(a[i]\) ,以此取得最优解。

以上的分析过后,\(l\)\(r\) 数组取值如下:

\(a = [1,2,3,5,4,1,2]\)
\(l = [1,2,3,4,4,1,7]\)
\(r = [6,5,5,4,5,6,7]\)

可以发现,当单调递增时, \(l[i]\) 单调递增,与 \(i\) 保持一致,否则与上一个大于等于自己的 \(l[i]\) 一致,当重新回到单调递增时, \(l[i]\) 开始重新轮回。 \(r[i]\) 此时也同理,只不过为倒序。这样首尾相接保持联系的状态很适合用这个单元的内容——栈,来解决。

下图形象地展示了这个过程:
image

因此在程序中,我们采用栈的方式来保持栈首与栈尾之间的数不超过他们,此时栈首也就是 \(l[i]\) 的取值了

for(int i=1;i<=n;i++) {
	while(!stkl.empty()&&a[stkl.top()]>=a[i]) stkl.pop();
	if(!stkl.empty()) l[i]=stkl.top()+1;
	else l[i]=1;
	stkl.push(i);
}

\(r[i]\) 的道理也同上,具体参见代码。

终 于 写 完 了

posted @ 2024-11-25 22:29  Severj  阅读(291)  评论(0)    收藏  举报