luogu p3834
維護乙個陣列(\([1 , n]\)), 可以在任意區間查詢第 \(k\) 小值(\(m\) 次).
\(1 \leq n \leq 2 * 10^5\), \(1 \leq n \leq 2 * 10^5\), \(-10^9 \leq a_i \leq 10^9\)
統計區間內各數出現次數, 從小到大統計, 直到小於等於當前數的數字個數剛好大於等於 \(k\).
為了提高效率, 可以用字首和維護和查詢每個數在不同區間出現的次數.
即使這樣, 查詢時也要從小到大列舉每個數, 所以考慮實現二分查詢, 將列舉數字的個數從 \(o(n)\) 優化到 \(o(logn)\)
在陣列值域上建立線段樹, 單點儲存該值出現的次數. 查詢時, 左子樹的值 \(\geq k\), 則要找的數在左子樹, 反之在右子樹.
權值線段樹的根節點區間是值域 \([-10^9, 10^9]\), 很顯然, 空間一定不夠. 就算是動態開點線段樹, 插入乙個點也需要大約 \(30\) 個節點.
引入離散化, 最多隻會出現 \(n\) 個數字, 將線段樹根的區間優化到 \([1,2 * 10^5]\)
如果給每個位置都建一棵權值線段樹, 空間一定不夠用, 但是思考每乙個位置的線段樹相對前乙個位置的線段樹的區別只是某點權值加一, 想到可持久化.
參見可持久化陣列
在處理某位置的字首和的時候, 從上乙個位置的線段樹繼承過來, 在新版本上修改.
時空複雜度 \(o((n + m)logn)\)
由於原始狀態線段樹為空, 所以動態開點的可持久化線段樹只需要新建乙個根節點即可.
在特定的點加上\(1\), 同步遍歷當前版本和前乙個版本, 批判地繼承.
void chg(node *x, node *y, unsigned int l, const unsigned int &r) else
if (l == r)
unsigned int m = (l + r) >> 1; //繼續遞迴
if (b <= m) else
} else else //繼承左兒子
} return;
}
兩個版本(\(l - 1\), \(r\)) 二分查詢.
由於某些指標為空, 所以可能有時會re
, 所以進行一系列操作來避免.
void qry(node *x, node *y, unsigned int l, const unsigned int &r)
unsigned int m = (l + r) >> 1, tmpx(0), tmpy(0);
node *sonxl(null), *sonxr(null), *sonyl(null), *sonyr(null);
if (x)
if (x->r)
} if (y)
if (y->r)
} //防re, 提前特判
if (c <= tmpy - tmpx)
c += tmpx; //右邊
c -= tmpy;
return qry(sonxr, sonyr, m + 1, r); //遞迴右兒子
}
某些**部分已省略.
int main()
sort(b + 1, b + n + 1);
b[0] = 0x3f3f3f3f;
for (register int i(1); i <= n; ++i)
} vrsn[0] = n;
for (register int i(1); i <= n; ++i)
for (register int i(1); i <= m; ++i)
return 0;
}
P3834 模板 可持久化線段樹 2(主席樹)
題目鏈結 靜態查詢區間第k小問題。我們先要知道,權值線段樹可以維護整體區間的桶,查詢整體區間第k小。雖然這題是查詢子區間 l,r 的第k小,但我們可以轉化成整體區間第k小問題 我們對每個字首都建一棵權值線段樹,用於維護出現個數。則 1,r 區間對應的線段樹減去 1,l 1 對應的線段樹後,得到的那棵...
P3834 模板 可持久化線段樹 1(主席樹)
主席樹 菜雞看了乙個晚上的時間才懂。感覺網上的部落格大都大亂。我也是找到了幾遍好一點的看了一下。參考部落格 參考部落格 首先,會主席樹的前提是會線段樹。還有,我們一般在用線段樹的時候,下標位置大都是採用了i 2 和i 2 1來作為i號的左右孩子。但是,在主席樹中,我們不可以把這種思想帶過來。因為主席...
P3834 模板 可持久化線段樹 1(主席樹)
這是個非常經典的主席樹入門題 靜態區間第k小 資料已經過加強,請使用主席樹。同時請注意常數優化 如題,給定n個整數構成的序列,將對於指定的閉區間查詢其區間內的第k小值。第一行包含兩個正整數n m,分別表示序列的長度和查詢的個數。第二行包含n個整數,表示這個序列各項的數字。接下來m行每行包含三個整數l...