[bzoj3638]k-Maximum Subsequence Sum

题目大意

给定一个序列,需要兹瓷两个操作:
1、修改一个元素的值
2、询问一个区间内选取不超过k个(不同询问的k不同但不会超过20)互不相交的子段和的最大值。

想想网络流

我们可以这样建图
把i拆成i和i’,然后i->i’一条容量为1(代表最多选一次)费用为ai的边。i’->i+1一条容量为1费用为0的边。s->i容量为1费用为0,i->t容量为1费用为0。那么我们现在就是要求总流量不超过k的最大费用流。做法是每一次找最长增广路,如果权值和小于0直接退出,否则加进答案并将沿途经过的所有边取反。
每一次最多增广1的流量,因此限定最多做k轮即可。

线段树维护

上面的网络流方法有很好的启发作用,我们并不需要真的跑网络流,而是直接模拟其算法过程,每一轮:
1:找到最大子段和,如果小于0就退出
2:加入答案,并将该段元素全部取相反数
因此我们只需要打一个能够维护区间最大(或小)(左或右或无)子段和的值(或左右端点)以及区间和。
为什么要维护最小值?因为区间取反后原来的最小值的相反数变成了取反后的最大值。
然后就上吧!

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
struct suan{
    int l,r,s;
    friend suan operator +(suan a,suan b){
        suan c;
        c.l=a.l;c.r=b.r;
        c.s=a.s+b.s;
        return c;
    }
    friend bool operator <(suan a,suan b){
        return a.s<b.s;
    }
};
struct dong{
    suan smax,lmax,rmax,smin,lmin,rmin,all;
    bool rev;
};
dong merge(dong a,dong b){
    dong c;
    c.smax=max(a.smax,b.smax);
    c.smax=max(c.smax,a.rmax+b.lmax);
    c.smin=min(a.smin,b.smin);
    c.smin=min(c.smin,a.rmin+b.lmin);
    c.lmax=max(a.lmax,a.all+b.lmax);
    c.lmin=min(a.lmin,a.all+b.lmin);
    c.rmax=max(b.rmax,a.rmax+b.all);
    c.rmin=min(b.rmin,a.rmin+b.all);
    c.all=a.all+b.all;
    c.rev=0;
    return c;
}
dong tree[maxn*5],sta[100],zlt;
int i,j,k,l,r,t,n,m,ans,top;
dong newnode(int x,int y){
    dong c;
    c.rev=0;
    c.smax.s=c.smin.s=c.lmax.s=c.lmin.s=c.rmax.s=c.rmin.s=c.all.s=y;
    c.smax.l=c.smin.l=c.lmax.l=c.lmin.l=c.rmax.l=c.rmin.l=c.all.l=x;
    c.smax.r=c.smin.r=c.lmax.r=c.lmin.r=c.rmax.r=c.rmin.r=c.all.r=x;
    return c;
}
void mark(int p){
    tree[p].rev^=1;
    swap(tree[p].smax,tree[p].smin);
    swap(tree[p].lmax,tree[p].lmin);
    swap(tree[p].rmax,tree[p].rmin);
    tree[p].smax.s*=-1;
    tree[p].smin.s*=-1;
    tree[p].lmax.s*=-1;
    tree[p].lmin.s*=-1;
    tree[p].rmax.s*=-1;
    tree[p].rmin.s*=-1;
    tree[p].all.s*=-1;
}
void down(int p,int l,int r){
    int mid=(l+r)/2;
    if (tree[p].rev){
        mark(p*2);
        mark(p*2+1);
        tree[p].rev=0;
    }
}
void change(int p,int l,int r,int a,int b){
    if (l==r){
        tree[p]=newnode(l,b);
        return;
    }
    down(p,l,r);
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a,b);else change(p*2+1,mid+1,r,a,b);
    tree[p]=merge(tree[p*2],tree[p*2+1]);
}
void reverse(int p,int l,int r,int a,int b){
    if (l==a&&r==b){
        mark(p);
        return;
    }
    down(p,l,r);
    int mid=(l+r)/2;
    if (b<=mid) reverse(p*2,l,mid,a,b);
    else if (a>mid) reverse(p*2+1,mid+1,r,a,b);
    else reverse(p*2,l,mid,a,mid),reverse(p*2+1,mid+1,r,mid+1,b);
    tree[p]=merge(tree[p*2],tree[p*2+1]);
}
dong query(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return tree[p];
    down(p,l,r);
    int mid=(l+r)/2;
    if (b<=mid) return query(p*2,l,mid,a,b);
    else if (a>mid) return query(p*2+1,mid+1,r,a,b);
    else return merge(query(p*2,l,mid,a,mid),query(p*2+1,mid+1,r,mid+1,b));
}
int main(){
    scanf("%d",&n);
    fo(i,1,n){
        scanf("%d",&t);
        change(1,1,n,i,t);
    }
    scanf("%d",&m);
    while (m--){
        scanf("%d",&t);
        if (!t){
            scanf("%d%d",&j,&k);
            change(1,1,n,j,k);
        }
        else{
            scanf("%d%d%d",&l,&r,&k);
            ans=top=0;
            while (k--){
                zlt=query(1,1,n,l,r);
                if (zlt.smax.s<0) break;
                ans+=zlt.smax.s;
                sta[++top]=zlt;
                reverse(1,1,n,zlt.smax.l,zlt.smax.r);
            }
            printf("%d\n",ans);
            while (top){
                reverse(1,1,n,sta[top].smax.l,sta[top].smax.r);
                top--;
            }
        }
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值