Competitive Programming Note
108 師大附中校隊培訓
本文已停止更新,新版請至 WiwiHo 的競程筆記
前綴和序列不能做修改,因為做一次修改就要用 的時間來重新算出整個序列,也就是說,每一個位置都被太多前綴和所包含了。
雖然帶修改前綴和也可以用線段樹來做,不過有一個更適合用在前綴和的可修改結構—樹狀數組/二元索引樹(Binary Indexed Tree,常簡稱 BIT)。BIT 的常數小,實作也更簡單。
BIT 的空間只要 ,比線段樹的 小,而和前綴和一樣,只需要用一個一維陣列就可以儲存一棵 BIT 了。
BIT 的每一個位置表示的值根據它的位置編號而定, 定義為:以 為結尾,長度為 的區間和,也就是 這個範圍的和, 指的是把 轉成二進位後,把最低位的 以外的 都變成 ,例如
所以說, 就是 這個區間的和。
要得到 的方法是 x&-x
,也就是把它和它的相反數取 bitwise and,因為 -x
會是 x
的補數加 ,x
在最低位的是 的位元會變為 ,右邊的 在取補數後會變為 ,加上 之後,這些又會變為 ,而本來是最低位的 的地方也會變為 ,但在左邊的每一個位元都會和本來不同,因為取 bitwise and 後,就可以得到 。
節點 代表的是一個空區間,而節點 的區間只包含 ,因此,序列的索引應該要從 開始。
那麼 BIT 畫成樹是什麼樣子?其實它並不是二元樹,binary 指的是二進位。
節點 就是根節點,然後按照上述規則,可以畫出一棵樹。仔細觀察一下,會發現每個節點代表的區間有這些性質:
在知道這些性質後,就可以來討論怎麼處理詢問了。
如果現在要修改一個位置 ,那要找到所有區間包含這個位置的節點,第一個這樣的節點是 ,接下來,它的所有右兄弟節點都包含這裡,用 可以得到右兄弟節點,而其父節點的右兄弟節點也包含這個點,若 最右邊的兄弟節點是 ,那其父節點的右兄弟會是 。
所以只要一直把 加上 ,就可以得到它和它祖先的所有右兄弟節點,修改這些節點的答案就好了。
實作相當簡單:
可以發現 會不斷增加,所以複雜度是 。
要查詢以 為結尾的前綴,就應該找到幾段不重疊,且聯集恰好是所求前綴的區間。
區間以 為結尾的節點是 ,而節點 的區間剛好緊接在它的父節點之後,它的父節點是 ,所以只要找到 和它所有祖先節點,這些區間聯集起來就是我們想要的前綴。
實作也非常簡單:
同樣地, 會不斷增加,因此複雜度是 。
建 BIT 的話,就把每一個元素分別 modify 就好了,複雜度是 。
和線段樹一樣,在 維 BIT 的每個節點上存一棵 維的 BIT,用法就和線段樹完全一樣。
BIT 最基礎的應用就是拿來算前綴和,因為和是一種「可返回」的東西,減去本來的值在加上新的值就可以得到新的和,而有了前綴和就也可以算區間和了,且它比線段樹好寫很多。
BIT 前綴和和差分序列結合使用,就可以做到 區間修改、 單點查詢。
BIT 也可以區間修改、區間查詢前綴和。先算出要做的序列 的差分序列 , 的索引從 開始,然後我們可以得出 這個區間的和是:
然後可以把它化成:
那麼只要用兩個 BIT,一個維護 、另一個維護 ,這樣就可以做區間修改、區間求和了。
至於 BIT 可不可以做最大最小值這種「不可返回」的操作呢?如果在求最大值的數字時,把其中一個位置改小,那麼是沒辦法單靠被修改的位置就得出新的最大值的,因此如果要用 BIT 做最大值,數字只能被改大,做最小值,數字只能被改小。