[SNOI2020] 区间和

一、题目

点此看题

二、解法

修改操作是区间取最大值,考虑用势能线段树来维护。关键的问题是,如果修改值 \(x\) 介于最小值和次小值之间时,需要快速地修改。

维护最大子段和不是很常规,回忆普通线段树做最大子段和的过程,主要是维护每个点的 最大前缀 和 最大后缀,这样就可以拼出最大子段。那么我们只需要在最小值变化时描述区间内所有 最大前缀 和 最大后缀 的变化,就可以描述最大子段。换句话说,如果当前线段树节点的子树内,有节点的最大前缀或最大后缀要变,就要找到它并修改它。

下面讲解如何处理最大前缀的变化(最大后缀同理),考虑随着区间取 \(\max\) 的进行,最大前缀的长度不降,而最大前缀的总长是 \(O(n\log n)\) 的,这说明我们只需要快速找到需要修改的节点,然后暴力修改即可。

考虑两种可能成为最优解的前缀,大小分别是 \(s_1,s_2(s_1>s_2)\),包含最小值数分别是 \(v_1,v_2(v_2>v_1)\),考虑何时 \(2\) 会替换 \(1\)

\[s_1-s_2<(v_2-v_1)\cdot \Delta \Rightarrow \frac{s_1-s_2}{v_2-v_1}<\Delta \]

设阈值 \(h_i\) 表示子树内最小的 \(\frac{s_1-s_2}{v_2-v_1}\)\(1\) 是取左儿子的最大前缀,\(2\) 是取整个左儿子加上右儿子的最大前缀),它可以在上传的时候维护。那么在做势能线段树时,如果 \(x<mi+\Delta\),说明最大前缀不会变,可以直接修改;否则可以暴力递归。

由于一个点最大前缀变化时,最大代价是从根花费 \(O(\log n)\) 次找到它,所以时间复杂度 \(O(n\log ^2n+q\log n)\)

具体实现中,pre,suf,mx,all 这些东西都要额外记录最小值和最小值个数,所以新开一个结构体代码会得到简化。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
const int N = M<<2;
const int inf = 0x3f3f3f3f;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,mi[N],ci[N],h[N],ad[N];
struct node
{
	int v,c;ll s;
	node(int V=0,int C=0,ll S=0) : v(V),c(C),s(S){}
	//if min=x , return the number of minimum
	int ask(int x) const {return (v==x)*c;}
	//if min=x , update the min and the sum
	void add(int x,int d) {if(v==x) v+=d,s+=(ll)c*d;}
	bool operator < (const node &b) const
		{return s!=b.s?s<b.s:c<b.c;}
	node operator + (const node &b) const
	{
		node r;
		r.s=s+b.s;r.v=min(v,b.v);
		r.c=ask(r.v)+b.ask(r.v);
		return r;
	}
}px[N],sx[N],mx[N],sum[N];
struct zz
{
	ll pre,suf,mx,sum;
	zz operator + (const zz &b) const
	{
		zz r;
		r.pre=max(pre,sum+b.pre);
		r.suf=max(b.suf,b.sum+suf);
		r.mx=max(suf+b.pre,max(mx,b.mx));
		r.sum=sum+b.sum;
		return r;
	}
};
void init(int i,int x)
{
	mi[i]=x;ci[i]=inf;ad[i]=0;
	sum[i]=node(x,1,x);
	if(x<=0) px[i]=sx[i]=mx[i]=node(inf,0,0),h[i]=1;
	else px[i]=sx[i]=mx[i]=sum[i],h[i]=inf;
}
int upd(node &x,node &y,node &z,int v)
{
	ll s=x.s-y.s-z.s,d=y.ask(v)+z.ask(v)-x.ask(v);
	if(s>0 && d>0) return min((ll)inf,s/d+1+v);
	return inf;
}
void up(int i)
{
	int x=i<<1,y=i<<1|1;
	h[i]=min(h[x],h[y]);
	mi[i]=min(mi[x],mi[y]);
	ci[i]=min(ci[x],ci[y]);
	if(mi[i]!=mi[x]) ci[i]=min(ci[i],mi[x]);
	if(mi[i]!=mi[y]) ci[i]=min(ci[i],mi[y]);
	//
	px[i]=max(px[x],sum[x]+px[y]);
	sx[i]=max(sx[y],sum[y]+sx[x]);
	sum[i]=sum[x]+sum[y];
	mx[i]=max(sx[x]+px[y],max(mx[x],mx[y]));
	h[i]=min(h[i],upd(px[x],px[y],sum[x],mi[i]));
	h[i]=min(h[i],upd(sx[y],sx[x],sum[y],mi[i]));
}
void add(int i,int x)
{
	px[i].add(mi[i],x);sx[i].add(mi[i],x);
	sum[i].add(mi[i],x);mx[i].add(mi[i],x);
	mi[i]+=x;ad[i]+=x;
}
void down(int i)
{
	if(!ad[i]) return ;
	if(mi[i]==mi[i<<1]+ad[i]) add(i<<1,ad[i]);
	if(mi[i]==mi[i<<1|1]+ad[i]) add(i<<1|1,ad[i]);
	ad[i]=0;
}
void build(int i,int l,int r)
{
	if(l==r) {init(i,read());return ;}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	up(i);
}
void zxy(int i,int l,int r,int L,int R,int x)
{
	if(x<=mi[i] || L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		if(l==r) {init(i,x);return ;}
		if(x<ci[i] && x<h[i]) {add(i,x-mi[i]);return ;}
	}
	int mid=(l+r)>>1;down(i);
	zxy(i<<1,l,mid,L,R,x);
	zxy(i<<1|1,mid+1,r,L,R,x);
	up(i);
}
zz ask(int i,int l,int r,int L,int R)
{
	if(L<=l && r<=R)
		return zz{px[i].s,sx[i].s,mx[i].s,sum[i].s};
	int mid=(l+r)>>1;down(i);
	if(L>mid) return ask(i<<1|1,mid+1,r,L,R);
	if(R<=mid) return ask(i<<1,l,mid,L,R);
	return ask(i<<1,l,mid,L,R)+ask(i<<1|1,mid+1,r,L,R);
}
signed main()
{
	n=read();m=read();
	build(1,1,n);
	while(m--)
	{
		int op=read(),l=read(),r=read();
		if(op==0) zxy(1,1,n,l,r,read());
		else
		{
			zz t=ask(1,1,n,l,r);
			printf("%lld\n",t.mx);
		}
	}
}
posted @ 2022-07-08 08:36  C202044zxy  阅读(380)  评论(2编辑  收藏  举报