1. 程式人生 > >bzoj3272 Zgg吃東西(線段樹模擬費用流)

bzoj3272 Zgg吃東西(線段樹模擬費用流)

題目連結

題目大意
給定數列{an},要求維護以下操作和詢問:

  • ai賦值為val
  • 在區間[l,r]中選出最多k個互不相交的子段列,最大化這些選中的數的和,輸出這個最大值

操作和詢問共m

分析:
首先看一下暴力怎麼解決這個問題:
把一個數拆成兩個點,作為X部和Y
X部的點向Y部的對應點連邊,容量為1,費用為ai
原點向X部連邊,容量為1,費用為0
Y部向匯點連邊,容量為1,費用為0
相鄰點從Y部連向X部,容量為1,費用為0
k次流量為1的最大費用流即可

上述方法邊數有O(n2),顯然會TLE
那麼我們就需要看出演算法的實質:
每次增廣的過程,實質上就是取一段和最大的子序列

,並將其反轉
很顯然對於這類序列上的操作,可以用線段樹去實現

正解

費用流的構圖,線段樹手動模擬增廣過程

線段樹維護方法:
維護一段區間的最大子序列

每次我們提取出一個最大子序列時,我們要把這個子序列取反(*-1,防止重複選擇),所以還需要維護最小子序列

每進行一次取反,當前最大和子序列一定變成最小和子序列,最小和子序列一定變成最大,那麼直接swap一下就可以了

鑑於一次詢問需要增廣K次,每一次都要要取反,所以需要開一個棧記錄一下當前詢問所反轉的所有區間,在結束時還原

總的時間複雜度是O(knlogn)

看一下維護:

struct node{
    int
lx,rx,mx,sum; int lp,rp,p1,p2; void init(int l,int val) { lp=rp=p1=p2=l; lx=rx=mx=val; sum=val; } }; struct Tree{ int l,r,a,b; bool flag; node mn,mx; void init(int val) { mn.init(l,-val); mx.init(l,val); } }; Tree t[N<<2];

lx:從左端點開始的最大子序列
rx:從右端點開始的最大子序列
mx:整個區間的最大子序列
sum:區間和
lplx的右端點
rprx的左端點
p1mx的左端點
p2mx的右端點

flag:區間翻轉標記
mn:記錄區間的最大子序列
mx:記錄區間的最小子序列

init函式:插入一個值(mx正值,mn負值)

void push(int bh) {
    if (t[bh].l==t[bh].r) return;
    if (t[bh].flag) {
        swap(t[bh<<1].mx,t[bh<<1].mn);
        swap(t[bh<<1|1].mx,t[bh<<1|1].mn);
        t[bh<<1].flag^=1; t[bh<<1|1].flag^=1;
        t[bh].flag^=1;
    }
}

處理區間翻轉:mn<=>mx

node merge(node a,node b) {
    node t;
    t.sum=a.sum+b.sum;

    t.lx=a.lx; t.lp=a.lp;
    if (a.sum+b.lx>t.lx) t.lx=a.sum+b.lx,t.lp=b.lp;

    t.rx=b.rx; t.rp=b.rp;
    if (b.sum+a.rx>t.rx) t.rx=b.sum+a.rx,t.rp=a.rp;

    t.mx=a.rx+b.lx; 
    t.p1=a.rp; t.p2=b.lp;
    if (t.mx<a.mx) t.mx=a.mx,t.p1=a.p1,t.p2=a.p2;
    if (t.mx<b.mx) t.mx=b.mx,t.p1=b.p1,t.p2=b.p2;
    return t;
}

重要的合併函式
按照各變數的意義轉移即可

void solve(int l,int r,int k) {
    int ans=0;
    top=0;
    while (k--) {
        node t=ask(1,l,r);
        if (t.mx>0) ans+=t.mx;
        else break;
        rever(1,t.p1,t.p2);
        ++top;
        q[top].x=t.p1; q[top].y=t.p2;
    }
    for (int i=top;i>0;i--)
        rever(1,q[i].x,q[i].y);       //消除影響 
    printf("%d\n",ans);
} 

每次我們提取出一個最大子序列t,加入答案(如果小於0就停止操作)
q是記錄翻轉區間的棧,處理完之後消除翻轉影響

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define ll long long

using namespace std;

const int N=100005;
struct node{
    int lx,rx,mx,sum;
    int lp,rp,p1,p2;
    void init(int l,int val) {
        lp=rp=p1=p2=l;
        lx=rx=mx=val;
        sum=val;
    }
};
struct Tree{
    int l,r,a,b;
    bool flag;
    node mn,mx;
    void init(int val) {
        mn.init(l,-val);
        mx.init(l,val);
    }
};
Tree t[N<<2];
struct point{
    int x,y;
};
point q[N];
int n,m,a[N],top=0;

void push(int bh) {
    if (t[bh].l==t[bh].r) return;
    if (t[bh].flag) {
        swap(t[bh<<1].mx,t[bh<<1].mn);
        swap(t[bh<<1|1].mx,t[bh<<1|1].mn);
        t[bh<<1].flag^=1; t[bh<<1|1].flag^=1;
        t[bh].flag^=1;
    }
}

node merge(node a,node b) {
    node t;
    t.sum=a.sum+b.sum;

    t.lx=a.lx; t.lp=a.lp;
    if (a.sum+b.lx>t.lx) t.lx=a.sum+b.lx,t.lp=b.lp;

    t.rx=b.rx; t.rp=b.rp;
    if (b.sum+a.rx>t.rx) t.rx=b.sum+a.rx,t.rp=a.rp;

    t.mx=a.rx+b.lx; 
    t.p1=a.rp; t.p2=b.lp;
    if (t.mx<a.mx) t.mx=a.mx,t.p1=a.p1,t.p2=a.p2;
    if (t.mx<b.mx) t.mx=b.mx,t.p1=b.p1,t.p2=b.p2;
    return t;
}

void update(int bh) {
    t[bh].mn=merge(t[bh<<1].mn,t[bh<<1|1].mn);
    t[bh].mx=merge(t[bh<<1].mx,t[bh<<1|1].mx);
}

void build(int bh,int l,int r) {
    t[bh].l=l; t[bh].r=r;
    if (l==r) {
        t[bh].init(a[l]);
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid+1,r);
    update(bh);
}

void rever(int bh,int L,int R) {
    push(bh);
    int l=t[bh].l,r=t[bh].r,mid=(l+r)>>1;
    if (l>=L&&r<=R) {
        swap(t[bh].mn,t[bh].mx);
        t[bh].flag^=1;
        return;
    }
    if (L<=mid) rever(bh<<1,L,R);
    if (R>mid) rever(bh<<1|1,L,R);
    update(bh);
}

node ask(int bh,int L,int R) {
    push(bh);
    int l=t[bh].l,r=t[bh].r,mid=(l+r)>>1;
    if (l==L&&r==R) return t[bh].mx;
    if (R<=mid) return ask(bh<<1,L,R);
    else if (L>mid) return ask(bh<<1|1,L,R);
    else return merge(ask(bh<<1,L,mid),ask(bh<<1|1,mid+1,R));
}

void solve(int l,int r,int k) {
    int ans=0;
    top=0;
    while (k--) {
        node t=ask(1,l,r);
        if (t.mx>0) ans+=t.mx;
        else break;
        rever(1,t.p1,t.p2);
        ++top;
        q[top].x=t.p1; q[top].y=t.p2;
    }
    for (int i=top;i>0;i--)
        rever(1,q[i].x,q[i].y);       //消除影響 
    printf("%d\n",ans);
} 

void change(int bh,int pos,int val) {
    push(bh);
    int l=t[bh].l,r=t[bh].r,mid=(l+r)>>1;
    if (l==r) {
        t[bh].init(val); return;
    }
    if (pos<=mid) change(bh<<1,pos,val);
    else change(bh<<1|1,pos,val);
    update(bh);
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,1,n);
    scanf("%d",&m);
    int opt,l,r;
    while (m--) {
        scanf("%d%d%d",&opt,&l,&r);
        if (opt==1) {
            int x; scanf("%d",&x);
            solve(l,r,x);
        }
        else change(1,l,r);
    }
    return 0;
}