bzoj3638&&【GDOI2016模拟3.20】diyiti

Description

给出一个长度为n的序列,a1~an,和m次操作,每次操作分为
0 x val,将ax变成val
1 l r k,询问在区间l~r中,最多k个不重合区间的最大和是多少。
n,m<=10^5,|ai|<=500,1操作<=10000,|val|<=500,1<=k<=20

Solution

首先,可以肯定的是这道题一定是某种神奇的数据结构,不过好像很难维护。
咦?
如果询问区间一定,这东西真的不是网络流吗?!

最大费用最大流

把每个点i拆成i和i’,从i向i’连容量为1,费用为ai的边。从i’向i+1连容量为1,费用为0的边。从源点向点i连容量为1,费用为0的边,从每个点i’向汇点连容量为1,费用为0的边,那么每次增广的流量都为1,做最多k次就是答案了。(当增广费用小于0是便可退出)。
这道题就这样解决了!?

线段树

上面的方法一看就会爆炸。
发现每次增广的都一定是最大和子序列。
于是我们就可以用线段树来模拟网络流。
每一次找出最大和子序列,然后将其取反。
这道题就这样解决了。
注意实现。
%%%WerKeyTom_FTD
神奇的结构体套结构体黑科技
常数爆炸。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 100005
using namespace std;
struct doit{
    int l,r,v;
    friend doit operator +(doit y,doit z) {
        doit x;x.v=y.v+z.v;x.l=y.l;x.r=z.r;return x;
    }
    friend bool operator <(doit y,doit z) {return y.v<z.v;}
};
struct note{
    bool bz;
    doit mx,mi,lmx,lmi,rmx,rmi,sum;
}t[N*3];
int n,x,l,r,k,m,ans,tot;
doit d[205];
note merge(note y,note z) {
    note x;
    x.sum=y.sum+z.sum;x.bz=0;
    x.mx=max(y.mx,max(z.mx,y.rmx+z.lmx));
    x.mi=min(y.mi,min(z.mi,y.rmi+z.lmi));
    x.lmx=max(y.lmx,y.sum+z.lmx);
    x.lmi=min(y.lmi,y.sum+z.lmi);
    x.rmx=max(z.rmx,y.rmx+z.sum);
    x.rmi=min(z.rmi,y.rmi+z.sum);
    return x;
}
void back(int v) {
    t[v].bz^=1;
    swap(t[v].mx,t[v].mi);
    swap(t[v].lmx,t[v].lmi);
    swap(t[v].rmx,t[v].rmi);
    t[v].sum.v*=-1;
    t[v].mx.v*=-1;t[v].mi.v*=-1;
    t[v].lmi.v*=-1;t[v].lmx.v*=-1;
    t[v].rmi.v*=-1;t[v].rmx.v*=-1;
}
void down(int v) {
    if (t[v].bz) t[v].bz=0,back(v*2),back(v*2+1);
}
void change(int v,int l,int r,int x,int y) {
    if (l==r) {
        t[v].bz=0;
        t[v].sum.v=t[v].lmi.v=t[v].lmx.v=t[v].mi.v=
        t[v].mx.v=t[v].rmi.v=t[v].rmx.v=y;
        t[v].sum.l=t[v].lmi.l=t[v].lmx.l=t[v].mi.l=
        t[v].mx.l=t[v].rmi.l=t[v].rmx.l=l;
        t[v].sum.r=t[v].lmi.r=t[v].lmx.r=t[v].mi.r=
        t[v].mx.r=t[v].rmi.r=t[v].rmx.r=l;
        return;
    }
    int m=(l+r)/2;down(v);
    if (x<=m) change(v*2,l,m,x,y);else change(v*2+1,m+1,r,x,y);
    t[v]=merge(t[v*2],t[v*2+1]);
}
note find(int v,int l,int r,int x,int y) {
    if (l==x&&r==y) return t[v];
    int m=(l+r)/2;down(v);
    if (y<=m) return find(v*2,l,m,x,y);
    else if (x>m) return find(v*2+1,m+1,r,x,y);
    else return merge(find(v*2,l,m,x,m),find(v*2+1,m+1,r,m+1,y));
}
void refuse(int v,int l,int r,int x,int y) {
    if (l==x&&r==y) {back(v);return;}
    int m=(l+r)/2;down(v);
    if (y<=m) refuse(v*2,l,m,x,y);
    else if (x>m) refuse(v*2+1,m+1,r,x,y);
    else refuse(v*2,l,m,x,m),refuse(v*2+1,m+1,r,m+1,y);
    t[v]=merge(t[v*2],t[v*2+1]);
}
int main() {
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&x),
    change(1,1,n,i,x);
    for(scanf("%d",&m);m;m--) {
        scanf("%d",&x);
        if (!x) {scanf("%d%d",&l,&r);change(1,1,n,l,r);continue;}
        ans=0;
        for(scanf("%d%d%d",&l,&r,&k);k;k--) {
            note sum=find(1,1,n,l,r);if (sum.mx.v<0) break;
            ans+=sum.mx.v;d[++tot]=sum.mx; 
            refuse(1,1,n,sum.mx.l,sum.mx.r);
        }
        printf("%d\n",ans);
        for(;tot;tot--) refuse(1,1,n,d[tot].l,d[tot].r);
    }
}

(话说CSDN的内置语言是神马?怎么c++变成了这幅鬼样!强迫症不爽)

你可能感兴趣的:(线段树,网络流,bzoj3638,GDOI2016模拟,diyiti)