题解:AT_ndpc2026_g 口
题意概述:
给你长度为
令长度为
感觉这个
不难发现几个结论:
-
从某一点
x 出发,一直向一个方向走,到y 结束,一定比从x,y 中间的某一点z 出发,先到x ,再折返回去到y 要不劣。 -
当你到达一个正整数时,一定要一直停留在这里,将其变为
0 ,然后选择继续走或结束。 -
当你处于一段连续的正整数的一端时,一定要将这一段正整数全部变为
0 ,到达另一端,然后选择继续走或结束。 -
当你下一步会到达
0 时,要么不走(结束),要么走,会让它减成-1 ,然后一定不会停留在这里,必须继续走,把这段连续的0 全部减成-1 ,并到达下一个正整数,然后执行上一步。
因此,必然是选择某段连续的正整数的一端,将这一段正整数全部变为
如果令所有的
用线段树实现单点修改,区间查询最大子段和,也是非常板了。
还是考虑初始答案为
上代码:
typedef long long ll;
const int N=1e5+10;
int n,q,a[N];
ll sum;
struct node
{
ll sum,maxl,maxr,ans;
}o[N*4];
node pushup(node l,node r)
{
node ret;
ret.sum=l.sum+r.sum;
ret.maxl=max(l.maxl,l.sum+r.maxl);
ret.maxr=max(r.maxr,r.sum+l.maxr);
ret.ans=max({l.ans,r.ans,l.maxr+r.maxl});
return ret;
}
void build(int x,int l,int r)
{
if(l==r)
{
o[x]=node{a[l],max(0ll,a[l]),max(0ll,a[l]),max(0ll,a[l])}; //注意!单点最大子段和至少为0,因为可以不操作
return ;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
o[x]=pushup(o[x<<1],o[x<<1|1]);
}
void change(int x,int l,int r,int t,int v)
{
if(l==r)
{
o[x]=node{v,max(0ll,v),max(0ll,v),max(0ll,v)};
return ;
}
int mid=(l+r)>>1;
if(t<=mid) change(x<<1,l,mid,t,v);
if(t>=mid+1) change(x<<1|1,mid+1,r,t,v);
o[x]=pushup(o[x<<1],o[x<<1|1]);
}
int main()
{
n=read(),q=read();
for(int i=1;i<=n;i++)
{
a[i]=read(),sum+=a[i];
if(!a[i]) a[i]=-1;
}
build(1,1,n);
for(int i=1;i<=n;i++) if(a[i]==-1) a[i]=0;
for(int i=1,x=0,y=0;i<=q;i++)
{
x=read(),y=read();
sum-=a[x],sum+=y,a[x]=y;
if(!y) y=-1;
change(1,1,n,x,y);
print(sum-o[1].ans),putchar('\n');
}
return 0;
}