【JZOJ】【单调栈】奶牛的歌声

奶牛合唱队的音量之谜
在 FarmerJohn 的奶牛合唱队中,每头奶牛根据其独特身高和音量唱歌,声音仅传至左右两侧更高奶牛。本篇探讨如何计算最高总音量并保护听力,采用单调栈算法解决复杂声音传播问题。

L i n k Link Link

J Z O J JZOJ JZOJ 1256 1256 1256

D e s c r i p t i o n Description Description

Farmer John的N(1<=N<=50,000)头奶牛喜欢站成一排一起唱歌(当然我们能听见的只是牛叫)。每头奶牛都有自己独特的身高h(1<=h<=2,000,000,000),并且唱歌时的音量为v(1<=v<=10,000)。每头奶牛的叫声都会从她所在的位置出发,向队列的两边传播(当然,站在队伍两端的奶牛例外)。并且,FJ注意到一个奇特的事实:当某头奶牛唱歌时,整个队伍中,在左右两个方向上,只有身高比她高且与她最接近的奶牛能听见她的歌声(也就是说,任何一头奶牛的叫声可能被0头、1头或2头奶牛听到,这取决于在这头奶牛的左右方向上有没有比她更高的奶牛)。
每头奶牛在唱歌时所听到的总音量,定义为她所能听见的所有其他奶牛歌声音量的和。考虑到某些奶牛(一般是较高的那些)听到的总音量很高,为了保护她们的听力,FJ决定为听到总音量最高的奶牛买副耳套。他想请你计算一下,在整个队列中,所听到总音量最高的那头奶牛听到的总音量的具体数值。

I n p u t Input Input

第1行: 一个正整数,N
第2…N+1行: 每行包括2个用空格隔开的整数,分别代表站在队伍中第i个位置的奶牛的身高以及她唱歌时的音量

O u t p u t Output Output

第1行: 队伍中的奶牛所能听到的最高的总音量

S a m p l e Sample Sample I n p u t Input Input

3
4 2
3 5
6 10

S a m p l e Sample Sample O u t p u t Output Output

7

H i n t Hint Hint

【样例说明】
队伍中有3头奶牛,第1头牛的身高是4,音量是2
其余依此类推。
队伍中的第3头奶牛可以听到第1头和第2头奶牛的歌声
于是她	能听到的总音量为2+5=7。
虽然她唱歌时的音量为10,但并没有奶牛可以听见她的歌声。

T r a i n Train Train o f of of T h o u g h t Thought Thought

正反两边单调栈
如果栈顶比当前牛高,将当前牛收入栈,遇见高的牛就出栈,然后累计起来,最后求最大值就好了

C o d e Code Code

#include<iostream>
#include<cstdio>

using namespace std;

int f[50005];
int n, ans, top;

struct stack
{
	long long h;
	int v;
}Stack[50005];//栈

struct Cow
{
	long long h;
	int v;
}cow[50005];

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	  scanf("%d%d", &cow[i].h, &cow[i].v);
	for (int i = 1; i <= n; ++i) {
		while (Stack[top].h < cow[i].h && top)
		{
			f[i] += Stack[top].v;
			top--;
		}
		Stack[++top].h = cow[i].h; Stack[top].v = cow[i].v;
	}
	top = 0;
	for (int i = n; i >= 1; --i)
	{
		while (Stack[top].h < cow[i].h && top)
		{
			f[i] += Stack[top].v;
			top--;
		}
		Stack[++top].h = cow[i].h; Stack[top].v = cow[i].v;
	}
	for (int i = 1; i <= n; ++i)
	 ans = max(ans, f[i]);
	printf("%d", ans);
}

<think>根据题目描述,我们需要解决“奶牛歌声 Mooo”问题。题目大意是:有N头奶牛排成一列,每头奶牛有一个身高h和音量v。每头奶牛的叫声会向左右两端传播,但只会被身高严格大于它的最近的一头奶牛听到(如果存在的话)。要求计算每头奶牛听到音量(即它左右两边比它高的且最近的奶牛听到音量之和),然后找出所有奶牛听到音量最大值。 解题思路: 我们可以使用单调栈来解决这个问题。具体步骤如下: 1. 从左向右扫描:维护一个单调递减栈(栈中存储的是奶牛的索引),这样当遇到一头新的奶牛时,如果它的身高大于栈顶奶牛的身高,那么栈顶奶牛的声音就会被当前奶牛听到(即当前奶牛是栈顶奶牛右边第一个比它高的),然后弹出栈顶,直到栈为空或者栈顶奶牛的身高大于当前奶牛。然后将当前奶牛入栈。 在这个过程中,我们可以将栈顶奶牛音量加到当前奶牛听到音量中(因为当前奶牛是栈顶奶牛右侧第一个比它高的,所以栈顶奶牛的声音会被当前奶牛听到?注意:题目传播是向两端的,但这里我们只处理右侧传播?不对,这里需要分两次处理:一次处理左边,一次处理右边。) 但是注意:题目中声音的传播方向是向两边的,而且每头奶牛只能被它左右两边严格大于它的最近奶牛听到。因此,我们需要分别计算每头奶牛被左边和右边听到的情况。 然而,题目要求的是每头奶牛听到音量(即它听到的所有其他奶牛音量之和)。但这里有一个关键:题目中说“每头奶牛的叫声向两端传播,但在每个方向都只会被身高严格大于它的最近的一头奶牛听到”。所以,一头奶牛的叫声会被它左右两边第一个比它高的奶牛听到(如果有的话)。那么,对于奶牛i,它的叫声会被谁听到?是左右两边第一个比它高的奶牛(如果有的话)会听到它的叫声。因此,我们需要计算的是:对于每头奶牛i,它的叫声会贡献给左右两边第一个比它高的奶牛(如果有的话)的音量。然后,我们要求的是每头奶牛(作为听众)所听到音量,即所有能够传播到它的叫声之和。 所以,我们需要: 1. 从左向右扫描:计算每头奶牛i的叫声被它右边第一头比它高的奶牛j听到,那么就把v[i]加到ans[j]上。 2. 从右向左扫描:计算每头奶牛i的叫声被它左边第一头比它高的奶牛j听到,那么就把v[i]加到ans[j]上。 最后,遍历ans数组,找出最大值。 因此,我们可以使用两次单调栈: 第一次(从左向右):维护一个单调递减栈(栈中存放奶牛下标),当遇到当前奶牛i的身高大于栈顶奶牛的身高时,说明栈顶奶牛的右边第一个比它高的就是i,那么将栈顶奶牛弹出,并将其音量加到ans[i]中。然后继续比较,直到栈为空或者栈顶奶牛的身高大于当前奶牛。然后将当前奶牛i入栈。 第二次(从右向左):同样维护一个单调递减栈,当遇到当前奶牛i的身高大于栈顶奶牛的身高时,说明栈顶奶牛的左边第一个比它高的就是i,那么将栈顶奶牛弹出,并将其音量加到ans[i]中。继续直到栈为空或者栈顶大于当前。然后入栈。 注意:两次扫描分别处理了左右两个方向。这样,每头奶牛作为听众,它听到音量就是左右两边比它矮的奶牛中,以它为最近高奶牛的那些奶牛音量之和。 但是,这里有一个细节:在第一次扫描(从左向右)时,我们处理的是每头奶牛右边的传播,即对于栈顶奶牛j,当前奶牛i是j右边第一个比它高的,所以j的叫声会被i听到,因此将v[j]加到ans[i]中。 同样,在第二次扫描(从右向左)时,对于栈顶奶牛j,当前奶牛i是j左边第一个比它高的,所以j的叫声会被i听到,因此将v[j]加到ans[i]中。 因此,算法步骤: 1. 初始化一个栈,用于存储奶牛的索引(下标)。 2. 从左向右遍历每头奶牛(i从1到n): while (栈非空 且 当前奶牛i的身高 > 栈顶奶牛的身高) 弹出栈顶奶牛j,将v[j]加到ans[i]上(因为j的叫声传播到右边,被i听到) 将当前奶牛i入栈。 3. 清空栈(或者重新初始化一个栈),然后从右向左遍历(i从n到1): while (栈非空 且 当前奶牛i的身高 > 栈顶奶牛的身高) 弹出栈顶奶牛j,将v[j]加到ans[i]上(因为j的叫声传播到左边,被i听到) 将当前奶牛i入栈。 4. 遍历ans数组,找出最大值。 注意:题目中每头奶牛作为听众,它听到音量是其他奶牛传播给它的,所以ans[i]初始为0。 根据引用[3]和[4]中的代码,也是采用类似的方法。 代码实现: 我们使用数组h[]存储身高,v[]存储音量,ans[]存储每头奶牛听到音量。 使用一个栈(可以用数组模拟)来存储奶牛的下标。 注意:栈中存储的是下标,这样我们可以通过下标访问身高和音量。 下面我们用C++实现: 步骤: 1. 读入n,然后读入n行,每行两个整数,分别表示h[i]和v[i]。 2. 初始化一个栈(用数组模拟),栈顶指针top=0。 3. 从左向右遍历i=1到n: while (top>0 且 h[i] > h[sta[top]]) { // sta[top]是栈顶元素的下标 ans[i] += v[sta[top]]; // 栈顶奶牛的声音被当前奶牛i听到 top--; // 弹出栈顶 } sta[++top] = i; // 将当前奶牛入栈 4. 重置栈(top=0),然后从右向左遍历i=n到1: while (top>0 且 h[i] > h[sta[top]]) { ans[i] += v[sta[top]]; top--; } sta[++top] = i; 5. 遍历ans数组,找出最大值max_ans,输出max_ans。 注意:在第二次遍历(从右向左)时,我们同样使用单调栈,但方向相反。 但是,在引用[3]的代码中,第二次遍历也是从n到1,但栈的操作和第一次类似。注意在第二次遍历时,栈中存储的是从右向左遍历时遇到的奶牛,所以栈中元素的身高是单调递减的(从栈底到栈顶)。 为什么使用单调栈?因为我们要找的是当前奶牛左边(或右边)第一个比它高的奶牛单调栈(递减)正好可以在O(n)时间内完成。 我们来写代码: 注意:数组下标从1开始(题目中奶牛编号1到n)。 示例输入: 3 4 2 3 5 6 10 示例输出:7 解释: 奶牛1(4,2)的叫声:左边没有比它高的,右边第一个比它高的是奶牛3(6>4),所以奶牛3会听到奶牛1的叫声(2)。 奶牛2(3,5)的叫声:左边第一个比它高的是奶牛1(4>3),所以奶牛1会听到5;右边第一个比它高的是奶牛3(6>3),所以奶牛3会听到5。 奶牛3(6,10)的叫声:左右都没有比它高的,所以没有奶牛听到它的叫声。 因此: 奶牛1听到音量=5(来自奶牛2) 奶牛2听到音量=0 奶牛3听到音量=2+5=7 最大值是7。 按照上面的算法: 第一次从左向右遍历: i=1: 栈空,入栈(栈:[1]) i=2: h[2]=3 < h[1]=4,所以不弹出,将2入栈(栈:[1,2]) i=3: h[3]=6 > h[2]=3 -> 弹出2,ans[3] += v[2]=5;然后比较h[3]=6>h[1]=4,弹出1,ans[3] += v[1]=2;栈空,入栈3(栈:[3]) 然后第二次从右向左遍历: i=3: 栈空,入栈(栈:[3]) i=2: h[2]=3 < h[3]=6,不弹出,入栈2(栈:[3,2]) i=1: h[1]=4 < h[2]=3? 不对,这里我们比较栈顶(当前栈顶是2,h[2]=3)和h[1]=4,因为4>3,所以弹出2,ans[1] += v[2]=5;然后栈顶变为3,h[3]=6>4,所以不弹出,入栈1(栈:[3,1]) 最后ans[1]=5, ans[3]=7, 最大值是7。 注意:第二次遍历时,我们处理的是左边的传播。在第二次遍历中,对于奶牛2,当遍历到1时,1比2高(4>3),所以奶牛2的叫声会被奶牛1听到(在第二次遍历中,当i=1时,栈顶是2,因为h[1]>h[2],所以弹出2,并将v[2]加到ans[1]上)。 因此,代码实现如下: 注意:数组大小要开够,根据题目N最大为50000。 我们使用数组模拟栈,定义sta数组,大小为N+10。 代码:</think>### 奶牛歌声 Mooo C++实现 #### 问题分析 给定 $N$ 头奶牛排成一列,每头奶牛有身高 $h_i$ 和音量 $v_i$。奶牛的叫声向左右传播,但只会被**身高严格大于它且最近的奶牛**听到。需要计算每头奶牛听到音量(左右传播到它的音量之和),并找出最大值。 **关键点**: - 声音传播规则:每头奶牛的叫声只能被左右两侧**第一个比它高的奶牛**听到 - 使用两次单调栈扫描: 1. **从左向右扫描**:处理右侧传播(计算每头奶牛被右侧更高奶牛听到音量) 2. **从右向左扫描**:处理左侧传播(计算每头奶牛被左侧更高奶牛听到音量) - 时间复杂度:$O(N)$,空间复杂度:$O(N)$ #### C++代码实现 ```cpp #include <iostream> #include <stack> #include <vector> #include <algorithm> using namespace std; int main() { int n; cin >> n; vector<int> h(n + 1), v(n + 1), ans(n + 1, 0); // 读入数据 for (int i = 1; i <= n; i++) { cin >> h[i] >> v[i]; } stack<int> st; // 单调递减栈(存储奶牛下标) // 从左向右扫描:处理右侧传播 for (int i = 1; i <= n; i++) { while (!st.empty() && h[i] > h[st.top()]) { ans[i] += v[st.top()]; // 当前奶牛听到栈顶奶牛的声音 st.pop(); } st.push(i); } // 清空栈,准备第二次扫描 while (!st.empty()) st.pop(); // 从右向左扫描:处理左侧传播 for (int i = n; i >= 1; i--) { while (!st.empty() && h[i] > h[st.top()]) { ans[i] += v[st.top()]; // 当前奶牛听到栈顶奶牛的声音 st.pop(); } st.push(i); } // 找出最大音量 int max_vol = 0; for (int i = 1; i <= n; i++) { max_vol = max(max_vol, ans[i]); } cout << max_vol << endl; return 0; } ``` #### 代码说明 1. **数据结构**: - `h[i]`:第 $i$ 头奶牛的身高 - `v[i]`:第 $i$ 头奶牛的原始音量 - `ans[i]`:第 $i$ 头奶牛听到音量(初始为0) - 单调栈 `st`:存储奶牛下标,保持栈内身高**单调递减** 2. **两次扫描**: - **向右扫描**(第1行到第 $n$ 行): - 当 $h[i] > h[\text{栈顶}]$ 时,栈顶奶牛的叫声会被 $i$ 听到,累加 $v[\text{栈顶}]$ 到 $ans[i]$ - 弹出栈顶直到栈空或 $h[i] \leq h[\text{栈顶}]$ - **向左扫描**(第 $n$ 行到第1行): - 同理处理左侧传播 - 注意扫描方向与栈操作对称 3. **复杂度**: - 每头奶牛最多入栈和出栈各一次,时间复杂度 $O(N)$ - 空间复杂度 $O(N)$ #### 示例测试 输入: ``` 3 4 2 3 5 6 10 ``` 处理过程: 1. 向右扫描: - 奶牛1(4,2)入栈 - 奶牛2(3,5)入栈(3<4) - 奶牛3(6,10)弹出奶牛2(3<6),$ans[3] +=5$;弹出奶牛1(4<6),$ans[3] +=2$ 2. 向左扫描: - 奶牛3(6,10)入栈 - 奶牛2(3,5)入栈(3<6) - 奶牛1(4,2)弹出奶牛2(3<4),$ans[1] +=5$ 3. 结果: - $ans[1]=5$,$ans[3]=7$,最大值输出 `7` 输出: ``` 7 ``` #### 应用场景 单调栈适用于需要**快速找到元素两侧第一个更大/更小值**的问题,如: 1. 直方图最大矩形面积 2. 接雨水问题 3. 股票跨度计算 [^1]: 单调栈经典应用:单向传播问题 [^2]: 双向扫描处理对称约束
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值