D - Yet Another Yet Another Task 用线段树写水题

a[i]的数据范围瞬间让这题变成了水题  我们枚举区间最大值 (从0到30) 负数没必要 因为当区间长度为1时ans为0 是最小值 我们只保留小于等于这个枚举值的数 然后维护最小前缀和就行了 如果碰到大于枚举值的位置 我们把最小前缀和设为正无穷   

#include<stdio.h>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int ans,b[N],sum[N];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&b[i]);
		sum[i]=sum[i-1]+b[i];
	}
	for(int i = 0; i <= 30; i++){
		int mn=2e9;
		for(int j = 1; j <= n; j++){
			if(b[j]>i){
				mn=2e9;
				continue;
			}
			mn=min(sum[j-1],mn);
			ans=max(ans,sum[j]-mn-i);
		}
	}
	printf("%d\n",ans);
}

不过如果a[i]没有这么小的范围限制呢,我们显然不能再去枚举最大值了。我们只能遍历数组,以每个位置为最大值统计答案 。首先要用单调栈求出每个位置左右最靠近的比它大的位置 例如数组 9 3 4 6 7 那么对于4来说 就是 位置1和位置5   用单调队列正向反向分别遍历就行  我们记为lt[i]和rt[i]  那么每个位置i的答案显然就是  lt[i]~i的最大后缀和加上i~rt[i]的最大前缀和减去a[i]  这一部分我是用线段树做的  用线段树维护前缀和数组sum[]的最大最小值  显然我们要求出lt[i]~i的最小前缀和以及i~rt[i]的最大前缀和 

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N],sum[N],tp,sta[N],lt[N],rt[N];
struct seg{
	int l,r;
	int mx,mi;
}T[N<<2];
const int inf = 1e9;
#define mid (T[id].l+T[id].r>>1)
#define ls id<<1
#define rs ls|1
#define lson ls,l,mid
#define rson rs,mid+1,r
void pushup(int id){
	T[id].mx=max(T[ls].mx,T[rs].mx);
	T[id].mi=min(T[ls].mi,T[rs].mi); 
}
void build(int id,int l,int r){
	T[id].l=l,T[id].r=r;
	if(l==r){
		T[id].mx=T[id].mi=sum[l];
		return;
	}
	int m = l+r>>1;
	build(ls,l,m);build(rs,m+1,r);
	pushup(id);
}
int query(int id,int L,int R,int op){
	if(L<=T[id].l&&R>=T[id].r){
		return op?T[id].mx:T[id].mi; 
	}
	int ans;
	ans=op?-inf:inf;
	if(L<=mid) ans=(op?max(ans,query(ls,L,R,op)):min(ans,query(ls,L,R,op)));
	if(R>mid) ans=(op?max(ans,query(rs,L,R,op)):min(ans,query(rs,L,R,op)));
	return ans;
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i = 1; i <= n; i++){
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	build(1,0,n);
	a[0]=a[n+1]=inf;
	sta[++tp]=0;
	for(int i = 1; i <= n; i++){
		while(tp>=1&&a[sta[tp]]<=a[i]) tp--;
		lt[i]=sta[tp];
		sta[++tp]=i;
	}
	tp=0;
	sta[++tp]=n+1;
	for(int i = n; i >= 1; i--){
		while(tp>=1&&a[sta[tp]]<=a[i]) tp--;
		rt[i]=sta[tp];
		sta[++tp]=i;
	}
/*	for(int i = 1; i <= n; i++){
		printf("lt[%d]=%d rt[%d]=%d\n",i,lt[i],i,rt[i]);
	}
*/
	int ans = 0;
	for(int i = 1; i <= n; i++){
		//printf("Q1=%d Q2=%d sum[i]=%d ",query(1,lt[i],i-1,0),query(1,i,rt[i]-1,1),sum[i]);
		ans=max(ans,-query(1,lt[i],i-1,0)+query(1,i,rt[i]-1,1)-a[i]);
		//printf("ans=%d\n",ans);
	}
	printf("%d\n",ans);
	return 0;
}

 队友(智杰巨巨)给了求后缀和最大值和前缀和最大值的方法,那就是拿线段树维护。在求区间最大子串和的时候 如果用线段树的话  我们都会维护最左最大子串和,最右最大子串和,区间最大子串和。那就可以直接从最左最大子串和,最右最大子串和下手了。一开始不知道怎么写query函数,后来智杰巨巨教我了。现在还是不很懂线段树能维护什么样的区间属性,看到过的是,这些属性要具有区间可加性。这个怎么说呢,如果能正确写出pushup函数的,应该都可以维护。做的题多了,能维护的东西就那些,但是线段树还是有很多很多匪夷所思的操作。还是得多学习。

#include<stdio.h>
#include<stack>
#include<algorithm>
using namespace std;
#define maxn 100010
int a[maxn];
int sum[maxn];
int x[maxn],y[maxn];
stack<int>s;
struct node{
	int l,r;
	int lmax,rmax;
};
node tree[400010];
void build(int k,int l,int r){
	tree[k].l=l;
	tree[k].r=r;
	if(l==r){
		tree[k].lmax=tree[k].rmax=a[l];
		return ;
	}
	int mid=(l+r)/2;
	build(2*k,l,mid);
	build(2*k+1,mid+1,r);
	tree[k].lmax=max(tree[2*k].lmax,sum[tree[2*k].r]-sum[tree[2*k].l-1]+tree[2*k+1].lmax);
	tree[k].rmax=max(tree[2*k+1].rmax,sum[tree[2*k+1].r]-sum[tree[2*k+1].l-1]+tree[2*k].rmax);
}
int query(int k,int l,int r,int flag){
	if(tree[k].l==l&&tree[k].r==r){
		if(flag==0)return tree[k].lmax;
		else return tree[k].rmax;
	}
	int mid=(tree[k].l+tree[k].r)/2;
	int ans;
	if(mid<r&&mid>=l){
		if(flag==0){
			ans=max(query(2*k,l,mid,flag),sum[mid]-sum[l-1]+query(2*k+1,mid+1,r,flag));
		}
		else{
			ans=max(query(2*k+1,mid+1,r,flag),sum[r]-sum[mid]+query(2*k,l,mid,flag));
		}
	}
	else if(mid>=r){
		ans=query(2*k,l,r,flag);
	}
	else{
		ans=query(2*k+1,l,r,flag);
	}
	return ans;
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	x[1]=1;
	s.push(1);
	for(int i=2;i<=n;i++){
		if(a[i]<=0)continue;
		while(!s.empty()&&a[s.top()]<=a[i]){
			s.pop();
		}
		if(s.empty()){
			x[i]=1;
		}
		else{
			x[i]=s.top()+1;
		}
		s.push(i);
	}
	while(!s.empty())s.pop();
	y[n]=n;
	s.push(n);
	for(int i=n-1;i>0;i--){
		if(a[i]<=0)continue;
		while(!s.empty()&&a[s.top()]<=a[i]){
			s.pop();
		}
		if(s.empty()){
			y[i]=n;
		}
		else{
			y[i]=s.top()-1;
		}
		s.push(i);
	}
	build(1,1,n);
	int ans=0;
	for(int i=1;i<=n;i++){
		if(a[i]<=0)continue;
		ans=max(query(1,x[i],i,1)+query(1,i,y[i],0)-2*a[i],ans);
	}
	printf("%d\n",ans);
}

猜你喜欢

转载自blog.csdn.net/weixin_43824564/article/details/106432499