保卫方案
题目描述
链接:https://www.nowcoder.com/questionTerminal/e1967ae812ea42e7a3ce57ee1f83b686
来源:牛客网
战争游戏的至关重要环节就要到来了,这次的结果将决定王国的生死存亡,小B负责首都的防卫工作。首都位于一个四面环山的盆地中,周围的n个小山构成一个环,作为预警措施,小B计划在每个小山上设置一个观察哨,日夜不停的瞭望周围发生的情况。
一旦发生外地入侵事件,山顶上的岗哨将点燃烽烟,若两个岗哨所在的山峰之间没有更高的山峰遮挡且两者之间有相连通路,则岗哨可以观察到另一个山峰上的烽烟是否点燃。由于小山处于环上,任意两个小山之间存在两个不同的连接通路。满足上述不遮挡的条件下,一座山峰上岗哨点燃的烽烟至少可以通过一条通路被另一端观察到。对于任意相邻的岗哨,一端的岗哨一定可以发现一端点燃的烽烟。 小B设计的这种保卫方案的一个重要特性是能够观测到对方烽烟的岗哨对的数量,她希望你能够帮她解决这个问题。
输入描述
输入中有多组测试数据,每一组测试数据的第一行为一个整数n(3<=n<=106),为首都周围的小山数量,第二行为n个整数,依次表示为小山的高度h(1<=h<=109).
输出描述
对每组测试数据,在单独的一行中输出能相互观察到的岗哨的对数。
示例1
输入
5
1 2 4 5
输出
7
求解思路
这个题目拿到之后看了好久不明白意思,翻看了别人的代码,在网上找了一些博客,在最终弄懂了,比较有用的博客链接为左神面试算法整理---单调栈, 原作者禁止转载和评论。
此题的关键之处在于:任意两个山峰(A)和(B), 若满足中间的任意山峰(C)的高度都低于(A)和(B),则可以互相传递信息,进而构成了一对组合。由于所有的观察哨在一个环上, 可转化为在环形链表中,求一个数(a)左右两侧离它最近且大于它的数(b)和(c),进而(a)与(b),(a)与(c)便是满足要求的组合。
考虑特殊情况,环形链表中的最大值和次大值,他们无法找到与之配对的两个数,只能与彼此组成一对组合。注意,以上情况假设无相同大小的数值出现,在这种情况下通解为:((n - 2) · 2 + 1)。
对于有重复数字的情况,(A(N_1), B(N_2), C(N_3), A > B space{} andspace{} C > B),(B)自身能够构成的组合数目有(C_{N_2}^2),即(N_2·(N_2-1)/2);(B)与(A),(B)与(C) 均能构成(N_2) 种组合,最终结果为 (N_2·(N_2-1)/2 + N_2 + N_2) 。
为了求出一个数左右都大于它的数,采用单调栈来实现,栈底到栈顶一次递减。在具体算法实现中,
-
遍历一次数组,找出相邻的相同元素,记录其重复次数,消除重复性(不相邻元素依然存在重复可能),同时记录数组最大值出现的位置(max_i);
-
创建堆栈(也可以是vector),从最大元素的位置开始遍历数组,执行如下操作:
- 堆栈为空, 将数组元素直接压入堆栈;
- 堆栈非空, 将要压入的元素
v[i]
与栈顶元素stack[top]
比较, 如果大于栈顶元素,加上栈顶元素的组合数目,然后弹出,重复此过程直至遇到大于v[i]
的元素,压入v[i]
; - 如果等于栈顶元素,将重复数目相加,继续执行;
- 如果小于栈顶元素,直接压入;
-
当数组遍历完之后,考虑堆栈的状态。如果不为空,每弹出一个元素,加上其组合数目,当堆栈只剩一个元素时(**最大值),有两种情况:
-
剩余元素的重复次数(n)大于1,此时次大元素能够组成的组合为 (N_2·(N_2-1)/2 + N_2 + N_2) ;
-
剩余元素重复次数为1,此时次大元素能够组成的组合为 $N_2·(N_2-1)/2 + N_2 ;
举个例子, AAAB连成一个环,B与其左侧的A可组成一个组合,与第一个A也能形成一个组合;但如果是AB的情况,则B只能与左侧的A形成组合,也就是说,从B看向A与从A看向B是不同的视角。
-
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct node{
int val;
long count;
node(int v, int c = 1): val(v), count(c){}
};
int main() {
ios::sync_with_stdio(false);
int N;
while(cin >> N){
vector<int> watch(N);
for(int i = 0; i < N; ++i)
cin >> watch[i];
vector<node> v;
node tmp(watch[0]);
int max_h = watch[0];
int max_index = 0;
for(int i = 1; i < N; ++i){
if(watch[i] == watch[i - 1])
tmp.count ++;
else{
v.push_back(tmp);
if( max_h < tmp.val){
max_h = tmp.val;
max_index = v.size() - 1;
}
tmp.val = watch[i];
tmp.count = 1;
}
}
// 最后一个元素
v.push_back(tmp);
if( max_h < tmp.val){
max_h = tmp.val;
max_index = v.size() - 1;
}
int n = 0;
long count = 0;
vector<node> stack;
for(int i = max_index; n < v.size(); ++n, i = (i+1)%v.size()){
while( stack.size() && v[i].val > stack[stack.size() - 1].val){
node & tmp = stack[stack.size() - 1];
count += tmp.count + tmp.count*(tmp.count - 1)/2;
stack.pop_back();
if(stack.size()) count += tmp.count;
}
if( stack.size()){
if(v[i].val == stack[stack.size() - 1].val)
stack[stack.size() - 1].count += v[i].count;
else
stack.push_back(v[i]);
} else
stack.push_back(v[i]);
}
while(stack.size()){
node & tmp = stack[stack.size() - 1];
count += tmp.count*(tmp.count - 1)/2;
stack.pop_back();
if(stack.size()) count += 2 * tmp.count;
if(stack.size()==1 &&stack[stack.size()-1].count==1) count-=tmp.count;
}
cout << count<<endl;
}
return 0;
}