1. 程式人生 > >【codevs 3981】動態最大子段和 / 線段樹

【codevs 3981】動態最大子段和 / 線段樹

給定一個長度為 n 的序列 ai,以及 q 次詢問,每次詢問給定 l,r 兩引數。
對於每次詢問,求 alar 之間的最大子段和,子段的意思是連續非空子區間。
更形式化地解釋:對於每次詢問給定的 l,r ,
求一個整數 ans,使得存在整數 l,r ,滿足llrri=lrai=ans
其中,n,q200000aiint 範圍內但不保證答案在 int 範圍內。

看題就知道要資料結構啦,
我們直接考慮合併,對於某個區間,它的兩個左右兒子區間怎樣合併為這個區間?
首先對於每個節點,我們肯定要記錄這個區間的最大子段和,記作

gss 。但是如果直接把兩個兒子的最大子段和取最大一定是錯的,因為一個區間的最大子段可能會跨過左右區間的分界點,而不是單獨分佈在左區間或者右區間。
為了維護這種情況,我們得多記兩個資訊:
以該區間左/右端點為起點的最大子段和,分別記作 lgss,rgss
如果能夠維護,那麼上述的另一種情況就也能被我們維護下來。我們只要把左區間的 rgss 加上右區間的 lgss ,就能得出跨分界點的最大子段和。
但我們在合併時要怎樣維護 lgssrgss 呢?由於兩個資訊是對稱的,我們拿 lgss 為例。
大區間的 lgs
s
一定等於左區間的 lgss 嗎?不一定。因為可能跨過分界點。
如果跨過分界點,其就是左區間的所有元素和加上右區間的 lgss 。所以我們還得在維護一個區間和,這個非常好維護。
因此這就是合併了,放個程式碼:

void Merge(R node &fa,R node &s1,R node &s2)
{
    fa.gss=max(max(s1.gss,s2.gss),s1.rgss+s2.lgss);
    fa.lgss=max(s1.lgss,s1.sum+s2.lgss);
    fa.rgss=max(s2.rgss,s2.sum
+s1.rgss); fa.sum=s1.sum+s2.sum; }

(區間 s1s2 合併為 fa 。)
理一理思路:

  • 每個節點記錄以下四個資訊:gss,lgss,rgss,sum。意義見上。
  • gss 的維護
  • 情況一:該區間的最大子段完全落在左子區間或右子區間。fa.gss=max(s1.gss,s2.gss)
  • 情況二·:該區間的最大子段跨過左右區間分界點。fa.gss=s1.rgss+s2.lgss
  • lgss 的維護(rgss 同理)
  • 情況一:該區間的 lgss 對應的子段完全落在左子區間。fa.lgss=s1.lgss
  • 情況二:該區間的 lgss 對應子段跨過分界點。fa.lgss=s1.sum+s2.lgss
  • sum 的維護: