題解 SP1716 【GSS3 - Can you answer these queries III】

\[ Preface \]數據結構

沒有 Preface。
\[ Description \]
維護一個長度爲 \(n\) 的數列 \(A\) ,須要支持如下操做:函數

  • 0 x y\(A_x\) 改成 \(y\)ui

  • 1 x y\(\max\limits_{x \leq l \leq r \leq y}{\sum_{i=l}^rA[i]}\)spa

\[ Solution \]code

區間最大子段和 是一個很是經典的問題。遞歸

對於 總體最大子段和 來講,通常有 \(O(n)\)貪心分治 作法,咱們討論的重點是 分治 作法。ip

\(~\)it

假設當前需求解最大子段和的區間是 \([l,r]\) ,令 \(mid=\left\lfloor\dfrac{l+r}{2}\right\rfloor\)io

咱們套路地把 \([l,r]\) 分紅 \([l,mid]\)\([mid+1,r]\) ,來進行分治求解:class

\(~\)

首先對於形如 \([l,l]\) 的區間,就十分好處理了,這裏很少說。

\(~\)

接下來,考慮下最大子段和滿不知足 區間可加性 ?(\([l,mid]\)\([mid+1,r]\) 的最大子段和可否推至 \([l,r]\)

顯然,只維護一個最大子段和,對 左子區間右子區間 的最大子段和取個 \(\max\) 是不能維護最大子段和的,由於其漏掉了 最大子段和同時包含左子區間和右子區間 的狀況。

那麼對於剩下的這一種狀況,它是一定通過中點 \(mid\) 的,那這種狀況的最大段就是 左子區間從右端點向左走的最大段右子區間從左端點向右走的最大段 的並集,其值爲 左子區間的後綴最大子段和 \(+\) 右子區間的前綴最大字段和

那咱們再維護 \(/\) 後 綴最大子段和 ,對其三者取 \(\max\) ,最大子段和就知足區間可加性了。

維護 \(/\) 後 綴最大子段和 依舊能夠分紅 通過 \(mid\) \(/\) 不通過 \(mid\) 來討論。

之前綴最大子段和爲例,若通過 \(mid\) ,則最大段爲 左子區間右子區間從左端點向右走的最大段 的並集,其值爲 左子區間和 \(+\) 右子區間的前綴最大子段和 ;若不通過 \(mid\) ,則最大段爲 左子區間從左端點向右走的最大段 。兩者取個 \(\max\) 便可。後綴最大子段和同理。

那咱們再維護個 區間和 ,那 最大子段和\(/\) 後 綴最大子段和 就都知足區間可加性了。

至於 區間和 \(......\) ,這玩意直接加就好了。

(上述內容你們能夠本身畫圖感性理解一下

\(~\)

求解過程

約定變量:

\(sum\) : 區間和

\(lmax\) : 區間前綴最大子段和

\(rmax\) : 區間後綴最大子段和

\(wmax\) : 區間最大子段和

設函數 \(ask(l,r)\) 求的是關於區間 \([l,r]\) 的一個四元組\((\) \(sum\), \(lmax\), \(rmax\), \(wmax\) \()\)

首先有一個遞歸邊界 \(l=r\) ,此時這四個元素均爲 \(A_l\)

那對於通常狀況,令 \(lc=ask(l,mid),rc=ask(mid+1,r)\) ,則有:
\[ self.sum=lc.sum+rc.sum \]

\[ self.lmax=\max(lc.lmax,lc.sum+rc.lmax) \]

\[ self.rmax=\max(rc.rmax,rc.sum+lc.rmax) \]

\[ self.wmax=\max(lc.wmax,rc.wmax,lc.rmax+rc.lmax) \]

此時 \(self\) 即爲 \(ask(l,r)\)

struct data{
    int sum;
    int lmax;
    int rmax;
    int wmax;
};

data ask(int l,int r)
{
    data self;
    if(l==r)
    {
        self.sum=self.lmax=self.rmax=self.wmax=A[l];
        return self;
    }
    int mid=(l+r)/2;
    data lc=ask(l,mid),rc=ask(mid+1,r);
    self.sum=lc.sum+rc.sum;
    self.lmax=max(lc.lmax,lc.sum+rc.lmax);
    self.rmax=max(rc.rmax,rc.sum+lc.rmax);
    self.wmax=max(max(lc.wmax,rc.wmax),lc.rmax+rc.lmax);
    return self;
}

\(~\)

而後你會發現,若對於每一個詢問都調用一次 ask(l,r) 會穩穩 T 掉

那我 bb 這麼多有什麼用呢

你們仔細想一想,這個分治的過程像不像某個數據結構呢?

線段樹?

線段樹!

是的,用線段樹維護,每一個節點保存的是該節點所表明的區間 \([l,r]\)\((\) \(sum\), \(lmax\), \(rmax\), \(wmax\) \()\)

剩下的都是一些線段樹基本操做了。
\[ Code \]

#include<cstdio>
#include<algorithm> 

#define RI register int

using namespace std;

const int SIZE=500100;

int n,m;
int a[SIZE];

struct SegmentTree{
    int l,r;
    int sum;
    int lmax;
    int rmax;
    int dat;
}t[SIZE*4];

void build(int p,int l,int r)
{
    t[p].l=l;t[p].r=r;
    if(l==r){t[p].sum=t[p].lmax=t[p].rmax=t[p].dat=a[l];return;}
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
    t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax);
    t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax);
    t[p].dat=max(max(t[p*2].dat,t[p*2+1].dat),t[p*2].rmax+t[p*2+1].lmax); 
}

void change(int p,int x,int val)
{
    if(t[p].l==t[p].r){t[p].sum=t[p].lmax=t[p].rmax=t[p].dat=val;return;}
    int mid=(t[p].l+t[p].r)/2;
    if(x<=mid)change(p*2,x,val);
    else change(p*2+1,x,val);
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
    t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax);
    t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax);
    t[p].dat=max(max(t[p*2].dat,t[p*2+1].dat),t[p*2].rmax+t[p*2+1].lmax); 
}

SegmentTree ask(int p,int l,int r)
{
    if(l<=t[p].l&&t[p].r<=r)return t[p];
    int mid=(t[p].l+t[p].r)/2;
    if(l<=mid&&mid<r)
    {
        SegmentTree lc=ask(p*2,l,r),rc=ask(p*2+1,l,r),self;
        self.sum=self.lmax=self.rmax=self.dat=0;
        self.sum=lc.sum+rc.sum;
        self.lmax=max(lc.lmax,lc.sum+rc.lmax);
        self.rmax=max(rc.rmax,rc.sum+lc.rmax);
        self.dat=max(max(lc.dat,rc.dat),lc.rmax+rc.lmax);
        return self;
    }
    if(l<=mid)
        return ask(p*2,l,r);
    if(mid<r)
        return ask(p*2+1,l,r);
}

int main()
{
    scanf("%d",&n);
    for(RI i=1;i<=n;i++)
        scanf("%d",&a[i]);

    build(1,1,n);

    scanf("%d",&m);

    while(m--)
    {
        char op[2];
        int l,r;
        scanf("%s%d%d",op,&l,&r);

        switch(op[0])
        {
            case '1':{
                if(l>r)swap(l,r);
                printf("%d\n",ask(1,l,r).dat);
                break;
            }

            case '0':{
                change(1,l,r);
                break;
            }
        }
    }
    return 0;
}

\[ Thanks \ for \ watching \]

相關文章
相關標籤/搜索