【cf补题】D. Max GEQ Sum

题意:

对于所有的(i,j),满足max \left (a_{i}, a_{i+1}, ..., a_{j-1}, a_{j}\right ) \geq a_{i}+a_{i+1}+...+a_{j-1}+a_{j}

( 1\leq i \leq j \leq n

思路:

  因为要知道 max \left (a_{i}, a_{i+1}, ..., a_{j-1}, a_{j}\right )的值,我们就可以以最大值为a_{i},往左和往右分别延伸到第一个比a_{i}大的数的后一个数和前一个数为一段区间,判断每一个这样的区间是否合法。假设这段区间为[x, y],i\epsilon [x,y],先考虑i的左半边,如果区间[x, i-1], [x+1, i-1], ..., [i-1, i-1]中区间和的最大值大于0,那么最大的这段区间和+a_{i}>a_{i},不满足条件;否则,最大的这段区间和+a_{i}<=a_{i}。考虑i的右半边也是一样,如果区间[i+1, i+1], ..., [i+1, y-1], [i+1, y]中区间和的最大值大于0,那么也不满足条件。

  对于每一个数,求在它后面的、比它大的第一个数:开一个栈和数组(假设它叫res[i],res[i]存放在i之后的、比第i个元素大的第一个数的下标),从前往后遍历数组,如果当前元素比栈顶元素大,那么就记录当前的位置,弹出栈顶,直到栈中没有元素或者栈顶元素大于等于当前元素,将当前元素的下标入栈。

  因为需要知道一段区间的最大值,可以用线段树来维护一段区间的最大值。假设线段树的某个点为i,那么i里存放的就是以i为结尾的前缀和,或者以i为开头的后缀和。如果是一条线段[a, b],那么存放的就是以a~b为结尾的前缀和或者以a~b为开头的的最大值。

  然后就是一些细节,线段树开多大,初始化线段树等等。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=1e18;

vector<ll> nextGreater(vector<ll> a, int n)
{
	stack<ll> st;
	vector<ll> res(n, n);
	for(int i=0; i<n; i++)
	{
		while(!st.empty() && a[st.top()]<a[i])
		{
			res[st.top()]=i;
			st.pop();
		}
		st.push(i);
	}
	return res;
}

vector<ll> prevGreater(vector<ll> a, int n)
{
	stack<ll> st;
	vector<ll> res(n, -1);
	for(int i=n-1; i>=0; i--)
	{
		while(!st.empty() && a[st.top()]<a[i])
		{
			res[st.top()]=i;
			st.pop();
		}
		st.push(i);
	}
	return res;
}

ll query(vector<ll> &tree, int p, int l, int r, int x, int y)
{
	if(y<l || x>r) return -inf;
	if(x<=l && r<=y) return tree[p];
	int mid=l+(r-l)/2;
	ll leftMax=query(tree, p*2, l, mid, x, y);
	ll rightMax=query(tree, p*2+1, mid+1, r ,x, y);
	return max(leftMax, rightMax);
}

int main(){
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		cin>>n;
		vector<ll> arr(n);
		vector<ll> preSum(n, 0), sufSum(n, 0);

		for(int i=0; i<n; i++) cin>>arr[i];	
		preSum[0]=arr[0];
		sufSum[n-1]=arr[n-1];
		for(int i=1; i<n; i++) preSum[i]=preSum[i-1]+arr[i];
		for(int i=n-2; i>=0; i--) sufSum[i]=sufSum[i+1]+arr[i];

		int _n=n;
		while(__builtin_popcount(_n)!=1) _n++;
		 
		vector<ll> preTree(2*_n, -inf), sufTree(2*_n, -inf);
		for(int i=0; i<n; i++)
		{
			preTree[_n+i]=preSum[i];
			sufTree[_n+i]=sufSum[i];
		}
		
		for(int i=_n-1; i>=1; i--)
		{
			preTree[i]=max(preTree[2*i], preTree[2*i+1]);
			sufTree[i]=max(sufTree[2*i], sufTree[2*i+1]);
		}
		
		vector<ll> neg=nextGreater(arr, n);
		vector<ll> preg=prevGreater(arr, n);
		
		bool flag=1;
		for(int i=0; i<n; i++)
		{
			ll rightMax=query(preTree, 1, 0, _n-1, i+1, neg[i]-1)-preSum[i];
			ll leftMax=query(sufTree, 1, 0, _n-1, preg[i]+1, i-1)-sufSum[i];
			if(max(rightMax, leftMax)>0)
			{
				flag=0;
				break;
			}
		}
		if(flag) puts("YES");
		else puts("NO");
		
	}
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值