ARC189 D 单调栈

一堆人,一个人可以吃相邻的的比他小的人,吃完体积相加。问每个人可以达到的最大体积?

首先不好想的话就想边界,这里的边界显然是最大的那个人,他可以吃掉所有人。然后对于一个一般的人,他首先可以吃掉两侧比他小的人,这可以单调栈求出两侧第一个更大的人,然后把中间的人都吃掉,这可以前缀和。然后再取挑战两侧更大的人,如果此时,比两侧原本更大的人还大了,就可以吃掉两侧更大的人,并且两侧更大的人能吃的,他现在一定也能吃,因此这个人的答案就应该是两侧更大的人的答案。如果还是没有两侧更大的人大,答案就是此时的大小。

那两侧更大的人的答案呢?我们可以根据初始大小排序,从大到小计算答案,这样就可以保证更大的人的答案已经算出来了,然后最开始,最大的那个人前面讨论过了,显然可以直接求出。

然后这里有个问题,如果吃完中间的,比两侧更大的人 l , r l,r l,r都大,应该取谁的答案作为当前答案?答案是都行,因为如果吃完中间的,比两侧更大的人都大,那么 s u m ( l + 1 , r − 1 ) > a l , s u m ( l + 1 , r − 1 ) > a r sum(l+1,r-1)>a_l,sum(l+1,r-1)>a_r sum(l+1,r1)>al,sum(l+1,r1)>ar

那么也就有 a l + s u m ( l + 1 , r − 1 ) > a ( r ) , a r + s u m ( l + 1 , r − 1 ) > a l a_l+sum(l+1,r-1)>a(r),a_r+sum(l+1,r-1)>a_l al+sum(l+1,r1)>a(r),ar+sum(l+1,r1)>al。也就是说 l , r l,r l,r在吃完中间这一段之后,都会比对方大, [ l , r ] [l,r] [l,r]这一段的人都可以合体,然后继续去吃外面的,从 l , r l,r l,r出发都可以到达这个合体的状态,所以 l , r l,r l,r的答案必然一样,所以取哪个都行。

如果中间的和之比一边的更大值大,就取这一边的。

void solve(){
	cin>>n;
	vi a(n+1);
	rep(i,1,n)cin>>a[i];
	vector<int>l(n+1,0),r(n+1,n+1);
	
	stack<int>s;
	for(int i=1;i<=n;i++){
	    while(s.size()&&a[i]>=a[s.top()]){
	        s.pop();
	    }
	    if(s.size())l[i]=s.top();
	    s.push(i);
	}
	
	s=stack<int>();
	for(int i=n;i>=1;i--){
	    while(s.size()&&a[i]>=a[s.top()]){
	        s.pop();
	    }
	    if(s.size())r[i]=s.top();
	    s.push(i);
	}
	vvi b;
	rep(i,1,n){
		b.push_back({a[i],i});
	}
	sort(b.begin(),b.end(),[](vi &x,vi &y){
		return x[0]>y[0];
	});
	
	vi sum(n+1),ans(n+1);
	rep(i,1,n)sum[i]=sum[i-1]+a[i];
	rep(i,0,n-1){
		int j=b[i][1];
		if(j==1){
			if(a[j+1]>=a[j]){
				ans[j]=a[j];
				continue;
			}
		}
		else if(j==n){
			if(a[j-1]>=a[j]){
				ans[j]=a[j];
				continue;
			}
		}
		else{
			if(a[j-1]>=a[j]&&a[j+1]>=a[j]){
				ans[j]=a[j];
				continue;
			}
		}
		int s=sum[r[j]-1]-sum[l[j]];
		if(l[j]>=1&&s>a[l[j]]){
			ans[j]=ans[l[j]];
		}
		else if(r[j]<=n&&s>a[r[j]]){
			ans[j]=ans[r[j]];
		}
		else{
			ans[j]=s;
		}
	}
	rep(i,1,n)cout<<ans[i]<<' ';
}	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值