2020-08-23

最优屏障–牛客原题

题目内容

来源:牛客网

题目描述
	M国的地势高低不平,现给出一个数组代表此国家某纬度上均匀分布的N座山的海拔高度H[i](任意两座山高度不
同),已知每座山的山顶上都有一座哨塔,若两个哨兵分别位于第i、j(i<j)座山上,当且仅当两人所在的山比两人之间
所有的山都高时,这两个哨兵可以相互监视,M国的防守能力大小为相互监视的哨兵对数。H国早已对M国虎视眈
眈,H国的皇帝希望黑魔法师们可以在M国的某两座山之间放置一块巨大的屏障,M国的哨兵不可通过该屏障互相监
视。皇帝想让你告诉他最优的屏障放置位置,你是皇帝手下最信任的军师,现在需要你帮助皇帝计算最优的屏障放
置位置和最大的防守力减少量。

输入描述:

第一行包含一个正整数T(T≤20)。
对于每组数据,第一行包含一个正整数n(2≤n≤50000)。
接下来n个不同的正整数,H1,H2,H3,…,Hn(0≤Hi≤109)分别代表横截面上每座山的海拔高度。
(读入数据比较大,建议使用scanf而不要使用cin读入)
对于60%的数据,n≤500
对于80%的数据,n≤5000
对于100%的数据,n≤50000

输出描述:

每组数据输出一行形如“Case #N: X C”,N代表当前是第N组数据(从1开始),X代表屏障放置在第X座山前可使M国
的防守能力下降最多, 此时减少量为C。若有多种方案使得减少量为C,那么输出最小的X对应的方案。

下面说一下思路:

  • 最开始有些同学可能会想到线段树等区间查询最大值的操作,不过时间复杂度可能会高达O(n2logn),所以不适合本题,在我掉入这个思路之后发现没有办法了,只能求助于网络,找到下面的思路,原回答不够清晰,我这里再详细解释一下(和有可能我太菜,看不太懂所以说人家的回答不够清晰)
  • 首先,在i位置加上了屏障后,分割出来的两段不相互影响,所以此时王国的防护能力减少量等于未加屏障时防护能力—两段防护能力。用这种方法求防护能力减少量是因为用下面的方法求每段防护能力较为方便。
  • 方法:由于满足 m a x ( h i + 1 , h i + 2 . . . . . . . . h j − 1 ) < m i n ( h i , h j ) max(h_{i+1},h_{i+2}........h_{j-1})<min(h_{i},h_{j}) max(hi+1,hi+2........hj1)<min(hi,hj)的i,j能相互看见,并且i,j的位置相互对称,也就是说i能看见j,j也能看见i,所以我们计算一方看见另一方就可以了,这样我们就可以调整计算顺序,我们可以试着求从左往右看的情况,由于往左看的时候只能看到比自己矮的山,并且由于自身比他们高,其他后续的山是看不到的,所以他们被看完就毫无作用了,这种模式可以用单调栈来处理,栈内元素从栈底到栈顶根据刚才的描述应该是逐渐减少的,,每次入栈时维护但单调栈的性质,维护时出栈的元素个数为当前元素能看见山的个数,最后我们要求的是刚刚对于每个元素所求的数量和。由于这种区间和我们要求好几次,所以想到用前缀和。
    加上屏障之后若我们只计算过从右往左看的前缀和,那么我们只能得到屏障左半段的前缀和,但是右半段由于前缀和受到左半段元素的影响不能计算独立的右半段,所以我们还要计算从左往右看的前缀和,(如果你看不懂我说的有可能你想的前缀和和我想的不一样,这里我的前缀和的相加顺序可以看程序的做法),这样就能计算右半锻的防护能力,而不受到左半段元素的影响。

代码如下

#include<iostream>
#include<cstring>
using namespace std;
const int maxx=5e4+5;
int v1[maxx],v2[maxx],a[maxx];
/*
	v1[i]表示从右向左看时[0,i]的前缀和
	v2[i]表示从左往右看时[i,n-1]的前缀和
	a[]表示山高
*/
int solve(int n,int &x)//坐标从0开始
{
	int stack[maxx];
	int top=-1;
	v1[0]=0;	//从0位置往左看一个也看不见
	stack[++top]=a[0];
	// cout<<"v1"<<endl;
	for(int i=1;i<=n-1;i++){
		int num=0;
		while(top!=-1&&stack[top]<a[i]){
			top--,num++;
		}
		if(top!=-1)v1[i]=v1[i-1]+num+1;
		else v1[i]=v1[i-1]+num;
		// cout<<v1[i]<<' '<<i<<endl;
		stack[++top]=a[i];
	}
	top=-1;
	v2[n-1]=0;//从最后一个位置看也是一个元素也看不见
	stack[++top]=a[n-1];
	// cout<<"v2"<<endl;
	for(int i=n-2;i>=0;i--){
		int num=0;
		while(top!=-1&&stack[top]<a[i])top--,num++;
		v2[i]=v2[i+1]+num+(top!=-1);
		// cout<<v2[i]<<' '<<i<<endl;
		stack[++top]=a[i];
	}
	int ans=0;
	for(int i=1;i<n;i++){	//寻找答案,在i的前面增加屏障
		if(ans<v1[n-1]-(v1[i-1]+v2[i])){
			ans=v1[n-1]-(v1[i-1]+v2[i]);
			x=i+1;
		}
	}
	return ans;
}
int main()
{
	// freopen("ini","r",stdin);
	int T;
	int t=0;
	cin>>T;
	while(t<T){
		t++;
		int n;
		scanf("%d",&n);
		for(int i=0;i<n;i++)scanf("%d",&a[i]);
		int x;		//用来表示位置
		int ans=solve(n,x);
		printf("Case #%d: %d %d\n",t,x,ans);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值