[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);
}
}
}