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;
}
posted @ 2023-10-06 15:43  xishanmeigao  阅读(18)  评论(0编辑  收藏  举报