传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2388
题解
一道貌似很恶心的分块题,调了好久。。
首先需要维护动态前缀和,对于一个点s,l< s< r,那么s需要增加的值为(s-l+1)*c,如果s位于首尾块内,那么就可以直接暴力修改,如果s位于中间的块内,那么对于每个块要记录出该块首项需要加的值,以及该块中各个点的公差。对于在r点右边的块,就可以直接打标记记录该块的每个点需要增加的值。这需要用三个数组来记录。
查询时,与l,r位于相同块的点直接暴力查询即可,位于l,r中间的块的点,需要在每个块内维护一个凸包,可以发现,如果斜率是凹的点一定不会是最优,这样就可以对于每一个块内进行二分查找。
code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
int n,m,q,block;
ll inf=1ll<<60;
ll a[N],sum[N];
ll dif[N];//记录每个块的首项需要加的值
ll fir[N];//记录每个块内的公差
ll add[N];
int pos[N],st[N],num[N],con[510][510];
double slop(int x,int y)
{
return (sum[x]-sum[y])/(x-y);
}
void reset(int x)
{
int l=(x-1)*block+1,r=min(n,x*block),top=0;
st[++top]=l;
for(int i=l+1;i<=r;i++)
{
while(top>=2&&slop(st[top-1],st[top])<slop(st[top-1],i))top--;
//维护块内凸包,使得满足答案单调性
st[++top]=i;
}
st[0]=0,st[top+1]=n+1,num[x]=top;
for(int i=0;i<=top+1;i++)con[x][i]=st[i];
//记录凸包的点
}
void PushDown(int x)
{
ll tmp=dif[x];
for(int i=(x-1)*block+1;i<=min(n,x*block);i++)
tmp+=fir[x],sum[i]+=tmp+add[x];
dif[x]=fir[x]=add[x]=0;
//对于一个需要用到的块暴力重构
}
void updata(int l,int r,ll c)
{
ll tmp=0;
if(pos[l]==pos[r])
{
PushDown(pos[l]);
for(int i=l;i<=r;i++)tmp+=c,sum[i]+=tmp;//暴力修改
for(int i=r+1;i<=min(pos[l]*block,n);i++)sum[i]+=tmp;//暴力修改
for(int i=pos[l]+1;i<=m;i++)add[i]+=tmp;//打标记
reset(pos[l]);
return ;
}
PushDown(pos[l]);//重构
for(int i=l;i<=pos[l]*block;i++)tmp+=c,sum[i]+=tmp;//暴力修改
reset(pos[l]);
for(int i=pos[l]+1;i<pos[r];i++)dif[i]+=tmp,fir[i]+=c,tmp+=c*(ll)block;//打标记
PushDown(pos[r]);
for(int i=(pos[r]-1)*block+1;i<=r;i++)tmp+=c,sum[i]+=tmp;
for(int i=r+1;i<=min(n,pos[r]*block);i++)sum[i]+=tmp;
reset(pos[r]);
for(int i=pos[r]+1;i<=m;i++)add[i]+=(ll)(r-l+1)*c;
}
ll calc(int x)
{
return sum[x]+dif[pos[x]]+fir[pos[x]]*(ll)(x-(pos[x]-1)*block)+add[pos[x]];
//计算某一点的具体值
}
ll find(int x)
{
int l=1,r=num[x];
while(l<=r)
{
int mid=l+r>>1;
ll t1=calc(con[x][mid-1]),t2=calc(con[x][mid]),t3=calc(con[x][mid+1]);
if(t1<t2&&t2<t3)l=mid+1;
else if(t1>t2&&t2>t3)r=mid-1;
else return t2;
}
//二分查找块内最值
}
ll query(int l,int r)
{
ll ans=-inf;
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)ans=max(ans,calc(i));
return ans;
}
for(int i=l;i<=pos[l]*block;i++)ans=max(ans,calc(i));
for(int i=(pos[r]-1)*block+1;i<=r;i++)ans=max(ans,calc(i));
for(int i=pos[l]+1;i<pos[r];i++)ans=max(ans,find(i));
return ans;
}
int main()
{
cin>>n;
block=(int)sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
pos[i]=(i-1)/block+1;
}
sum[0]=sum[n+1]=-inf;
if(n%block)m=n/block+1;
else m=n/block;
for(int i=1;i<=m;i++)reset(i);
cin>>q;
for(int i=1;i<=q;i++)
{
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(!op)
{
ll z;scanf("%lld",&z);
updata(x,y,z);
}else
printf("%lld\n",query(x,y));
}
return 0;
}