CF280D k-Maximum Subsequence Sum
(题目传送门)
题意:一个长度为 \(n\) 的序列,要求支持下列操作
- 单点修改某个数的值
- 求区间 \([l,r]\) 选出至多 \(k\) 个不相交的子段和的最大值
模拟费用流/反悔贪心的典题
从费用流的角度考虑。从源点 \(S\rightarrow i\) 连 \((1,0)\) 的边,\(i\rightarrow i+1\) 连 \((1,a_i)\) 的边,\(i\rightarrow T\) 连 \((1,0)\) 的边。这样,一滴 \(S\rightarrow i\rightarrow j\rightarrow T\) 的流量就代表选取了区间 \([i,j-1]\)。且每多流一滴流量,就多一个区间。所以将初始流量设为 \(k\),跑费用流即为答案。但这样显然会 \(\rm TLE\)
考虑増广的过程,找到一条费用最大的边进行増广,并建对应的反向弧。其实就相当于选出一个最大子段和的区间,并将它们取相反数(费用流反向弧的费用等于正向弧相反数)
因此,我们就可以使用模拟费用流来解决这个问题
从贪心的角度思考,每次都选择最大子段和,但选中的数可能有些不是最优解,因此我们的选择要能支持反悔。具体操作就是将选过的最大子段变成相反数,这样下次选中它们时就相当于反悔操作,不选这些数。这样操作就一定能取到最优解
\(k\) 次操作后,我们就能取到所有段数 \(\leq k\) 的最大值,那直接 \(k\) 次操作即可
现在考虑如何实现模拟费用流/贪心。它需要我们能支持查询区间最大子段和,取反最大子段,单点修改某个值。很容易想到线段树。因为有取反操作,所以还需要维护最小子段和,同时还需要维护最大/最小子段所代表的区间才能进行取反操作,写一个结构体会好写很多
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e5+10;
const LL INF=1e18;
int n,q,a[N];
LL ans;
queue < pair<int,int> > qq;
struct node{int l,r;LL s;}; node zinf={0,0,INF},finf={0,0,-INF};
node operator + (const node &a,const node &b){return {a.l,b.r,a.s+b.s};}
bool operator < (const node &a,const node &b){return a.s<b.s;}
#define lc(p) p<<1
#define rc(p) p<<1|1
struct Seg
{
node mx,mn,lmx,rmx,lmn,rmn,sum;
int rev;
#define rev(x) tree[x].rev
void write(int l,int r,LL s)
{
mx=mn=lmx=rmx=lmn=rmn=sum={l,r,s};
rev=0;
}
void reverse()
{
swap(mx,mn); swap(lmx,lmn); swap(rmx,rmn);
mx.s*=-1; mn.s*=-1;
lmx.s*=-1; rmx.s*=-1;
lmn.s*=-1; rmn.s*=-1;
sum.s*=-1; rev^=1;
}
}tree[N<<2];
Seg I={finf,zinf,finf,finf,zinf,zinf,{0,0,0},0};
Seg pushup(Seg p,Seg q)
{
Seg val=I;
if(p.mx.s>=1e16)
return q;
if(q.mx.s>=1e16)
return p;
val.mx=max(max(p.mx,q.mx),p.rmx+q.lmx);
val.mn=min(min(p.mn,q.mn),p.rmn+q.lmn);
val.lmx=max(p.lmx,p.sum+q.lmx);
val.rmx=max(q.rmx,p.rmx+q.sum);
val.lmn=min(p.lmn,p.sum+q.lmn);
val.rmn=min(q.rmn,p.rmn+q.sum);
val.sum=p.sum+q.sum;
return val;
}
void spread(int p)
{
if(rev(p))
{
tree[lc(p)].reverse();
tree[rc(p)].reverse();
rev(p)=0;
}
}
void build(int p,int l,int r)
{
if(l==r)
{
tree[p].write(l,r,a[l]);
return;
}
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
tree[p]=pushup(tree[lc(p)],tree[rc(p)]);
}
void change(int p,int l,int r,int x,int v)
{
if(l==r)
{
tree[p].write(l,r,v);
return;
}
spread(p);
int mid=(l+r)>>1;
if(x<=mid)
change(lc(p),l,mid,x,v);
else
change(rc(p),mid+1,r,x,v);
tree[p]=pushup(tree[lc(p)],tree[rc(p)]);
}
void reverse(int p,int l,int r,int ql,int qr)
{
if(ql<=l && qr>=r)
return tree[p].reverse(),void();
spread(p);
int mid=(l+r)>>1;
if(ql<=mid)
reverse(lc(p),l,mid,ql,qr);
if(qr>mid)
reverse(rc(p),mid+1,r,ql,qr);
tree[p]=pushup(tree[lc(p)],tree[rc(p)]);
}
Seg ask(int p,int l,int r,int ql,int qr)
{
if(ql<=l && qr>=r)
return tree[p];
spread(p);
int mid=(l+r)>>1;
Seg val=I,lval=I,rval=I;
if(ql<=mid)
lval=ask(lc(p),l,mid,ql,qr);
if(qr>mid)
rval=ask(rc(p),mid+1,r,ql,qr);
val=pushup(lval,rval);
return val;
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&q);
while(q--)
{
int op,l,r,k,x,v;
scanf("%d",&op);
if(op==0)
{
scanf("%d%d",&x,&v);
change(1,1,n,x,v);
}
else
{
scanf("%d%d%d",&l,&r,&k);
ans=0;
while(k--)
{
Seg cur=ask(1,1,n,l,r);
if(cur.mx.s<=0)
break;
ans+=cur.mx.s;
reverse(1,1,n,cur.mx.l,cur.mx.r);
qq.push({cur.mx.l,cur.mx.r});
}
printf("%lld\n",ans);
while(qq.size())
{
reverse(1,1,n,qq.front().first,qq.front().second);
qq.pop();
}
}
}
return 0;
}