CF280D k-Maximum Subsequence Sum【线段树+费用流】

题目描述

Consider integer sequence a1​,a2​,...,an​ . You should run queries of two types:

  • The query format is " 0 i val ". In reply to this query you should make the following assignment: ai​=val .
  • The query format is " 1 l r k ". In reply to this query you should print the maximum sum of at most k non-intersecting subsegments of sequence al​,al+1​,...,ar​ . Formally, you should choose at most k pairs of integers ((x1​,y1​),(x2​,y2​),...,(xt​,yt​) (l<=x1​<=y1​<x2​<=y2​<...<xt​<=yt​<=r; t<=k) such that the  sum ax1​+ax1​+1+...+ay1​+ax2​+ax2​+1+...+ay2​+...+axt​+axt​+1+...+ayt​ is as large as possible. Note that you should choose at most k subsegments. Particularly, you can choose 0 subsegments. In this case the described sum considered equal to zero.

输入格式

The first line contains integer n(1<=n<=1e5) , showing how many numbers the sequence has. The next line contains n integers a1​,a2​,...,an​ (∣ai​∣<=500) .

The third line contains integer m(1<=m<=1e5) — the number of queries. The next mm lines contain the queries in the format, given in the statement.

All changing queries fit into limits: 1<=i<=n , |val|<=500∣val∣<=500 .

All queries to count the maximum sum of at most k non-intersecting subsegments fit into limits:1<=l<=r<=n , 1<=k<=20 . It is guaranteed that the number of the queries to count the maximum sum of at most k non-intersecting subsegments doesn't exceed 10000 .

输出格式

For each query to count the maximum sum of at most k non-intersecting subsegments print the reply — the maximum sum. Print the answers to the queries in the order, in which the queries follow in the input.

题意翻译

长度为n 的数列,支持两种操作:
1.修改某个位置的值
2.询问区间[l,r] 里选出至多k 个不相交的子段和的最大值。 一共有m 个操作

感谢@Fheiwn 提供的翻译

输入输出样例

输入 #1

9
9 -8 9 -1 -1 -1 9 -8 9
3
1 1 9 1
1 1 9 2
1 4 6 3

输出 #1

17
25
0

输入 #2

15
-4 8 -3 -10 10 4 -7 -7 0 -6 3 8 -10 7 2
15
1 3 9 2
1 6 12 1
0 6 5
0 10 -7
1 4 9 1
1 7 9 1
0 10 -3
1 4 10 2
1 3 13 2
1 4 11 2
0 15 -9
0 13 -9
0 11 -10
1 5 14 2
1 6 12 1

输出 #2

14
11
15
0
15
26
18
23
8

说明/提示

In the first query of the first example you can select a single pair (1,9) . So the described sum will be 17.

Look at the second query of the first example. How to choose two subsegments? (1, 3) and (7, 9)? Definitely not, the sum we could get from (1, 3) and (7, 9) is 20, against the optimal configuration (1, 7) and (9, 9) with 25.

The answer to the third query is 0, we prefer select nothing if all of the numbers in the given interval are negative.

解题思路

首先考虑当k=1时怎么办

我们要用到的数据结构时线段树

 以其中一个节点now为例,如下图,a,b分别是now的子区间

我们让线段树的每个节点维护 4 个信息

1:从左端起的最大连续和 maxl[x]

2:从右端起的最大连续和 maxl[x]

3:整个区间的最大连续和 Max[x]

4:整个区间的所有元素之和 sum[x]

如图

如图假设我们已经知道了a,b区间的信息,怎么求出now的信息呢?

很显然 

sum[now]=sum[a]+sum[b]

Max[now]=max{ Max[a],Max[b] , maxr[a]+maxl[b] }//即要么是两个子区间各自的最大值,要么是穿过两个区间的最大值

同理 maxl[now]=max{ maxl[a] , sum[a]+maxl[b] }

maxr[now]=max{ maxr[b] , sum[b]+maxr[a] }

那么k!=1时怎么办呢?

需要用到模拟费用流(不过本蒟蒻并未学过

听老师讲了半天也只听懂了做法

先找出区间最大连续值,再把这个区间取反,然后再找最大连续值,一直进行K次,把每次的和累加起来就是答案。

那么我们需要维护什么呢?(比较多,请细细看)

sum,Max,Maxl,Maxr 同上所述

因为我们要调用最大区间并取反,所以我们要知道这个区间的左右端点 maxPL,maxPR

维护这个端点需要用Maxl和Maxr的端点 maxpl,maxpr

取反时的懒标记lazy

当我们取反时,最大会变成最小,最小会变成最大,因此我们还需要维护上述和元素相反的最小值

因此需要维护

L,R,sum,Max,maxPL,maxPR,Min,minPL,minPR,Maxl,maxpl,Minl,minpl,Maxr,maxpr,Minr,minpr,lazy

(真的有点多)

合并过程也很好理解了,如下图

inline Node merge(const Node &a, const Node &b)
{
    Node New;
    New.L=a.L;
	New.R=b.R;
    New.sum=a.sum+b.sum;
    if(a.Maxl>=a.sum+b.Maxl) 
	New.Maxl=a.Maxl,New.maxpl=a.maxpl;
    else New.Maxl=a.sum+b.Maxl,New.maxpl=b.maxpl;
    if(a.Minl<=a.sum+b.Minl) 
	New.Minl=a.Minl,New.minpl=a.minpl;
    else New.Minl=a.sum+b.Minl,New.minpl=b.minpl;
    if(b.Maxr>=b.sum+a.Maxr)
	New.Maxr=b.Maxr,New.maxpr=b.maxpr;
    else New.Maxr=b.sum+a.Maxr,New.maxpr=a.maxpr;
    if(b.Minr<=b.sum+a.Minr) 
	New.Minr=b.Minr,New.minpr=b.minpr;
    else New.Minr=b.sum+a.Minr,New.minpr=a.minpr;
    New.Max=a.Maxr+b.Maxl;
	New.maxPL=a.maxpr;
	New.maxPR=b.maxpl;
    if(New.Max<a.Max) 
	New.Max=a.Max,New.maxPL=a.maxPL,New.maxPR=a.maxPR;
    if(New.Max<b.Max)
	New.Max=b.Max,New.maxPL=b.maxPL,New.maxPR=b.maxPR;
    New.Min=a.Minr+b.Minl;New.minPL=a.minpr;New.minPR=b.minpl;
    if(New.Min>a.Min) 
	New.Min=a.Min,New.minPL=a.minPL,New.minPR=a.minPR;
    if(New.Min>b.Min) 
	New.Min=b.Min,New.minPL=b.minPL,New.minPR=b.minPR;
    New.lazy=0;
    return New;
}

反转过程也很好理解了,就是先把上述元素取反,在把最大值和最小值交换

inline void Rev(Node &a)
{
    a.lazy^=1;
    a.sum=-a.sum; 
    swap(a.Maxl,a.Minl);
	swap(a.maxpl,a.minpl);	   
	a.Maxl=-a.Maxl;
	a.Minl=-a.Minl; 
    swap(a.Maxr,a.Minr);
	swap(a.maxpr,a.minpr);	   
	a.Maxr=-a.Maxr;
	a.Minr=-a.Minr; 
	swap(a.maxPL,a.minPL);
	swap(a.maxPR,a.minPR);	   
	a.Max=-a.Max;
	a.Min=-a.Min;
    swap(a.Max,a.Min); 

}

初始化的时候要记得把所有代码都初始化

inline void build(int k,int l,int r)
{
    if(l==r)
	{
        tree[k].L=tree[k].R=l;
		tree[k].maxPL=tree[k].maxPR=l;
		tree[k].minPL=tree[k].minPR=l;
		tree[k].maxpl=tree[k].maxpr=l;
		tree[k].minpl=tree[k].minpr=l;
		
		tree[k].sum=tree[k].Max=tree[k].Min=a[l];
		tree[k].Maxl=tree[k].Maxr=a[l];
		tree[k].Minl=tree[k].Minr=a[l];	
		
		tree[k].lazy=0;
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    pushup(k);
}

还有其他一些细节,可以借助代码理解一下

完整代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
struct Node 
{
    int L,R,sum,Max,maxPL,maxPR,Min,minPL,minPR,Maxl,maxpl,Minl,minpl,Maxr,maxpr,Minr,minpr;
    bool lazy;
}tree[4*N],op[22];
inline Node merge(const Node &a, const Node &b)
{
    Node New;
    New.L=a.L;
	New.R=b.R;
    New.sum=a.sum+b.sum;
    if(a.Maxl>=a.sum+b.Maxl) 
	New.Maxl=a.Maxl,New.maxpl=a.maxpl;
    else New.Maxl=a.sum+b.Maxl,New.maxpl=b.maxpl;
    if(a.Minl<=a.sum+b.Minl) 
	New.Minl=a.Minl,New.minpl=a.minpl;
    else New.Minl=a.sum+b.Minl,New.minpl=b.minpl;
    if(b.Maxr>=b.sum+a.Maxr)
	New.Maxr=b.Maxr,New.maxpr=b.maxpr;
    else New.Maxr=b.sum+a.Maxr,New.maxpr=a.maxpr;
    if(b.Minr<=b.sum+a.Minr) 
	New.Minr=b.Minr,New.minpr=b.minpr;
    else New.Minr=b.sum+a.Minr,New.minpr=a.minpr;
    New.Max=a.Maxr+b.Maxl;
	New.maxPL=a.maxpr;
	New.maxPR=b.maxpl;
    if(New.Max<a.Max) 
	New.Max=a.Max,New.maxPL=a.maxPL,New.maxPR=a.maxPR;
    if(New.Max<b.Max)
	New.Max=b.Max,New.maxPL=b.maxPL,New.maxPR=b.maxPR;
    New.Min=a.Minr+b.Minl;New.minPL=a.minpr;New.minPR=b.minpl;
    if(New.Min>a.Min) 
	New.Min=a.Min,New.minPL=a.minPL,New.minPR=a.minPR;
    if(New.Min>b.Min) 
	New.Min=b.Min,New.minPL=b.minPL,New.minPR=b.minPR;
    New.lazy=0;
    return New;
}
inline void pushup(int k)
{	
	tree[k]=merge(tree[k<<1],tree[k<<1|1]);
}
inline void Rev(Node &a)
{
    a.lazy^=1;
    a.sum=-a.sum; 
    swap(a.Maxl,a.Minl);
	swap(a.maxpl,a.minpl);	   
	a.Maxl=-a.Maxl;
	a.Minl=-a.Minl; 
    swap(a.Maxr,a.Minr);
	swap(a.maxpr,a.minpr);	   
	a.Maxr=-a.Maxr;
	a.Minr=-a.Minr; 
	swap(a.maxPL,a.minPL);
	swap(a.maxPR,a.minPR);	   
	a.Max=-a.Max;
	a.Min=-a.Min;
    swap(a.Max,a.Min); 

}
inline void pushdown(int k)
{
    if(tree[k].lazy)
	{
        Rev(tree[k<<1]);
        Rev(tree[k<<1|1]);
        tree[k].lazy=0;
    }
}
int n,m,a[N];
inline void build(int k,int l,int r)
{
    if(l==r)
	{
        tree[k].L=tree[k].R=l;
		tree[k].maxPL=tree[k].maxPR=l;
		tree[k].minPL=tree[k].minPR=l;
		tree[k].maxpl=tree[k].maxpr=l;
		tree[k].minpl=tree[k].minpr=l;
		
		tree[k].sum=tree[k].Max=tree[k].Min=a[l];
		tree[k].Maxl=tree[k].Maxr=a[l];
		tree[k].Minl=tree[k].Minr=a[l];	
		
		tree[k].lazy=0;
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    pushup(k);
}
inline void update(int k,int l,int r,int x,int v)
{
    if(l==r)
	{
        tree[k].L=tree[k].R=l;
		tree[k].maxPL=tree[k].maxPR=l;
		tree[k].minPL=tree[k].minPR=l;
		tree[k].maxpl=tree[k].maxpr=l;
		tree[k].minpl=tree[k].minpr=l;
		
		tree[k].sum=tree[k].Max=tree[k].Min=v;
		tree[k].Maxl=tree[k].Maxr=v;
		tree[k].Minl=tree[k].Minr=v;	
		
		tree[k].lazy=0;		
        return;
    }    
	pushdown(k);
    int mid=(l+r)>>1;
    if(x<=mid) update(k<<1,l,mid,x,v);
    else update(k<<1|1,mid+1,r,x,v);
    pushup(k);
}
inline void Reverse(int k,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
	{
        Rev(tree[k]);
        return;
    }    
	pushdown(k);
    int mid=(l+r)>>1;
    if(L<=mid) Reverse(k<<1,l,mid,L,R);
    if(mid<R) Reverse(k<<1|1,mid+1,r,L,R);
    pushup(k);
}
inline Node query(int k,int l,int r,int L,int R)
{
    if(L<=l&&r<=R) return tree[k];
    pushdown(k);    
    int mid=(l+r)>>1;
    if(R<=mid) return query(k<<1,l,mid,L,R);
    else if(mid<L) return query(k<<1|1,mid+1,r,L,R);
    else return merge(query(k<<1,l,mid,L,R),query(k<<1|1,mid+1,r,L,R));
}
int read()
{
	int x=0,f=0;char ch=0;
	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return f?-x:x;
}
int main()
{
	freopen("kmaximumsubsequencesum.in","r",stdin);
	freopen("kmaximumsubsequencesum.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) 
	a[i]=read();
    build(1,1,n);
    m=read();
    int opt,l,r,k,ans,t;
    while(m--)
	{
        opt=read();
        l=read();
        r=read();
        if(opt==0)
        {
            update(1,1,n,l,r);    	
		}
        else
		{
            k=read();
            ans=0;t=k;
            for(int i=1;i<=k;i++)
			{
                op[i]=query(1,1,n,l,r);
                if(op[i].Max<=0)
				{
					t=i-1;
					break;
				}
                Reverse(1,1,n,op[i].maxPL,op[i].maxPR);
                ans+=op[i].Max;
            }
            for(int i=1;i<=t;i++) 
			Reverse(1,1,n,op[i].maxPL,op[i].maxPR);//取反之后记得再返回来
            printf("%d\n",ans);
        }
    }
}

PS:这是本人通过的第一道黑体,以次纪念

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值