周賽370。想半天才想通,結果動態開點線段樹模板效能不佳,最後一個測資跑不過。最後優化來不及,好虧啊。

題目

輸入整數陣列nums。

一個nums的長度k子序列,其包含的索引為i0 < i1 < … < ik-1
若滿足以下條件則稱為平衡的

  • 對於每個在[1, k-1]之間的j,滿足nums[ij] - nums[ij-1] >= ij - ij-1

長度為1的子序列永遠是平衡的。

求nums平衡子序列可以得到的最大元素總和

解法

對於j<i的nums[j]和nums[i]來說,只有nums[i]-nums[j] >= i-j,才可以把nums[i]接在nums[j]後面。

但是隨著i改變,每個i-j和nums[i]-nums[j]值都要重新計算,這樣複雜度會高達O(N^2)。
後來靈機一動,發現可以把式子變形:

nums[i]-nums[j] >= i-j
移項得到nums[i]-i >= nums[j]-j

把nums[i]-i看成一個變數,就叫他diff[i]好了。
如此條件就簡化成:只要diff[j]小於等diff[i],可則nums[i]接在nums[j]後面。

依序枚舉nums[i],找到diff[i]的值,記做d。
找到以小於等於d的nums[j]結尾的最大子序列總和best。則以nums[i]結尾的最大總和sub即是max(0,best)+nums[i]。
然後以sub更新以d結尾的子序列最大值。
遍歷完,答案就是所有d結尾的子序列最大值。

上述操作需要單點修改,區間查詢,使用線段樹來達成。
d的範圍大約在[-10^9, 10^9]之間,理論上動態開點線段樹應該也能過,誰知道就是會TLE。
最後我使用離散化+普通線段樹才通過。

時間複雜度O(N log N)。
空間複雜度O(N)。

class Solution:
    def maxBalancedSubsequenceSum(self, nums: List[int]) -> int:
        # 區間查詢
        # 回傳[i, j]的max
        def query(id, L, R, i, j):
            if i <= L and R <= j:  # 當前區間目標範圍包含
                return tree[id]
            ans = -inf
            M = (L+R)//2
            if i <= M:
                ans = max(ans,query(id*2, L, M, i, j))
            if M+1 <= j:
                ans  = max(ans,query(id*2+1, M+1, R, i, j))
            return ans


        # 單點更新
        # 對索引i改成val
        def update(id, L, R, i, val):
            if L == R:  # 當前區間目標範圍包含
                tree[id] = val
                return
            M = (L+R)//2
            if i <= M:
                update(id*2, L, M, i, val)
            else:
                update(id*2+1, M+1, R, i, val)
            tree[id] = max(tree[id*2],tree[id*2+1])  # 以左右子樹更新答案
            
        diff=[x-i for i,x in enumerate(nums)]
        mp={x:i for i,x in enumerate(sorted(set(diff)))}
        N=len(diff)+5
        tree = [-inf]*(N*4)
        for i,d in enumerate(diff):
            d=mp[d]
            best=query(1,0,N-1,0,d)
            sub=max(best,0)+nums[i]
            update(1,0,N-1,d,sub)
            
        return query(1,0,N-1,0,N-1)

雖然說是區間查詢,但事實每次查詢的起點都是0(離散化後對應diff最小值),也就是前綴最大值。
既然是前綴,可以改用常數更小的樹狀陣列

注意:樹狀陣列用來求極值時,只能求前綴極值。若是max,則值只能增大;求min,則值只能減少。

時間複雜度O(N log N)。
空間複雜度O(N)。

雖然複雜度相同,但執行時間不到線段樹的一半,空間也只需要1/4。

class Solution:
    def maxBalancedSubsequenceSum(self, nums: List[int]) -> int:
        diff=[x-i for i,x in enumerate(nums)]
        mp={x:i for i,x in enumerate(sorted(set(diff)))}
        N=len(diff)
        bit=BIT(N)
        for i,d in enumerate(diff):
            d=mp[d]
            best=bit.query(d)
            sub=max(best,0)+nums[i]
            bit.update(d,sub)
            
        return bit.query(N-1)
    
class BIT:
    """
    tree[0]代表空區間,不可存值,基本情況下只有[1, n-1]可以存值。
    offset為索引偏移量,若設置為1時正好可以對應普通陣列的索引操作。
    注意:只能查前綴極值。若求max則tree[i]值只能增、不能減。
    """

    def __init__(self, n, offset=1):
        self.offset = offset
        self.tree = [-inf]*(n+offset)

    def update(self, pos, val):
        """
        將tree[pos]設成val
        """
        i = pos+self.offset
        while i < len(self.tree):
            self.tree[i] = max(self.tree[i], val)
            i += i & (-i)

    def query(self, pos):
        """
        查詢[1, pos]的max
        """
        i = pos+self.offset
        res = -inf
        while i > 0:
            res = max(res, self.tree[i])
            i -= i & (-i)
        return res