BZOJ 3638 k-Maximum Subsequence Sum

题目大意:给一列数,要求支持操作: 1.修改某个数的值 2.读入l,r,k,询问在[l,r]内选不相交的不超过k个子段,最大的和是多少。

如果仅仅是询问一次,我们可以构图费用流,跑一次就行了。但题目是多组询问,直接费用流显然会TLE。自然地,我们想到对残余网络进行增广,但每次地询问区间不同,而且有修改操作,这种做法也不可行。本蒟蒻膜拜了一下hzwer大大的题解,发现根据费用流每次求最长路的性质,可以手动增广,也就是每次用线段树求一个最大子段和,然后将整个区间取反,继续增广,直到和为0或者个数达到K次为止。(可以这么理解,增广之后正向弧达到满流,反向弧流减少,而反向弧中费用为负值)接下来就是线段树的基本操作了。我们要维护区间最大和以及位置(正向弧和反向弧的信息都要维护),还要维护一个flag记录上次增广的是哪条弧,但只要想清楚还是很好写的。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=200000+10;
struct node
{
  int lx,lp,rx,rp,sum;
  int mx,p1,p2;
};
struct data
{
  node mx,mn;
  int flag;
}t[maxn*4];
struct arr
{
  int l,r;
};
vector<arr> h;
int n,m,op,a[maxn],cc;
void init(node &p,int l,int v)
{
  p.lp=p.rp=p.p1=p.p2=l;
  p.mx=p.lx=p.rx=p.sum=v;
}
node merge(node a,node b)
{
  node res;res.sum=a.sum+b.sum;
  res.lx=a.lx;res.lp=a.lp;
  if(a.sum+b.lx>res.lx) res.lx=a.sum+b.lx,res.lp=b.lp;
  res.rx=b.rx;res.rp=b.rp;
  if(b.sum+a.rx>res.rx) res.rx=b.sum+a.rx,res.rp=a.rp;
  res.mx=a.mx;res.p1=a.p1;res.p2=a.p2;
  if(b.mx>res.mx) res.mx=b.mx,res.p2=b.p2,res.p1=b.p1;
  if(a.rx+b.lx>res.mx) res.mx=a.rx+b.lx,res.p1=a.rp,res.p2=b.lp;
  return res;
}
void pushup(int p)
{
  t[p].mx=merge(t[p*2].mx,t[p*2+1].mx);
  t[p].mn=merge(t[p*2].mn,t[p*2+1].mn);
}
void pushdown(int p)
{
  if(t[p].flag)
  {
    swap(t[p*2].mn,t[p*2].mx);
    swap(t[p*2+1].mn,t[p*2+1].mx);
    t[p].flag^=1;t[p*2].flag^=1;t[p*2+1].flag^=1;
  }
}
void build(int p,int l,int r)
{
  if(l==r)
  {
    init(t[p].mx,l,a[l]);
    init(t[p].mn,l,-a[l]);
    return;
  }
  int mid=(l+r)>>1;
  build(p*2,l,mid);build(p*2+1,mid+1,r);
  pushup(p);
}
void update(int p,int l,int r,int x,int v)
{
  if(x<l||x>r) return;
  if(l==r)
  {
    init(t[p].mx,l,v);
    init(t[p].mn,l,-v);
    return;
  }
  pushdown(p);
  int mid=(l+r)>>1;
  update(p*2,l,mid,x,v);
  update(p*2+1,mid+1,r,x,v);
  pushup(p);
}
void reverse(int p,int l,int r,int x,int y)
{
  if(l>y||r<x) return;
  if(l>=x&&r<=y)
  {
    swap(t[p].mn,t[p].mx);
    t[p].flag^=1;
    return;
  }
  pushdown(p);int mid=(l+r)>>1;
  reverse(p*2,l,mid,x,y);
  reverse(p*2+1,mid+1,r,x,y);
  pushup(p);
}
node query(int p,int l,int r,int x,int y)
{
  if(l>=x&&r<=y) 
  {
    return t[p].mx;
  }
  int mid=(l+r)>>1;
  pushdown(p);
  if(y<=mid) return query(p*2,l,mid,x,y);
  else if(x>mid) return query(p*2+1,mid+1,r,x,y);
  else return merge(query(p*2,l,mid,x,y),query(p*2+1,mid+1,r,x,y));
}
void solve(int l,int r,int k)
{
  int res=0;h.clear();
  for(int i=1;i<=k;i++)
  {
    node hm=query(1,1,n,l,r);
    if(hm.mx<=0) break;
    else res+=hm.mx;
    reverse(1,1,n,hm.p1,hm.p2);
    h.push_back((arr){hm.p1,hm.p2});
    //cout<<hm.p1<<' '<<hm.p2<<endl;
  }
  for(int i=h.size()-1;i>=0;i--)
    reverse(1,1,n,h[i].l,h[i].r);
  printf("%d\n",res);
}
int main()
{
  //freopen("3638.in","r",stdin);
  //freopen("3638.out","w",stdout);
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&a[i]);
  build(1,1,n);int op,l,r,k;
  scanf("%d",&m);
  for(int i=1;i<=m;i++)
  {
    cc++;
    scanf("%d%d%d",&op,&l,&r);
    if(op!=1) update(1,1,n,l,r),a[l]=r;
    else
    {
      scanf("%d",&k);
      solve(l,r,k);
    }
  }
  return 0;
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值