日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

高级数据结构笔记

發布時間:2024/1/11 windows 24 coder
生活随笔 收集整理的這篇文章主要介紹了 高级数据结构笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

樹套樹

顧名思義,就是一個樹套一個樹。。。

廣義的樹套樹是指嵌套多層的數據結構。常見的有:線段樹套線段樹(二維線段樹),線段樹套平衡樹(“二逼平衡樹”),分塊套平衡樹,樹狀數組套線段樹(帶修主席樹)等等。

在這里,由于 set,map 等 STL 內部實現是平衡樹,因此將這些 STL 的嵌套也算作樹套樹。

樹套樹解決偏序問題

樹套樹最典型的應用就是解決各種各樣的偏序問題。

P3810 【模板】三維偏序(陌上花開)

經典解法是 CDQ 分治。這里使用樹狀數組套權值線段樹解決。

首先第一維是經典的排序,第二維可以使用樹狀數組維護起來。樹狀數組的每個節點維護一棵動態開點線段樹,維護這個節點范圍內所有節點的第三維信息。

時間復雜度 \(O(n \log ^ 2 n)\),空間 \(O(n \log n)\)。

放一下主體部分代碼:

struct node {
	int ls, rs, s;
}tr[M];
#define lc tr[u].ls
#define rc tr[u].rs
#define mid (l + r >> 1)
void add(int &u, int l, int r, int x) {
	if (!u) u = ++ cnt; tr[u].s ++ ; 
	if (l == r) return;
	if (x <= mid) add(lc, l, mid, x);
	else add(rc, mid + 1, r, x);
}
void ADD(int x, int y) {
	for (int i = x; i <= m; i += (i & -i)) add(rt[i], 1, m, y);
}
int sum(int &u, int l, int r, int x) {
	if (!u or l > x) return 0;
	if (r <= x) return tr[u].s;
	return sum(lc, l, mid, x) + sum(rc, mid + 1, r, x);
}
int SUM(int x, int y, int s = 0) {
	for (int i = x; i; i -= (i & -i)) s += sum(rt[i], 1, m, y); return s;
}

P3157 [CQOI2011] 動態逆序對

將刪點操作倒過來,就是逆序加點的過程。

將加點的順序(即時間軸)看做第一維,將下標看做第二維,將權值看做第三維。

這就是典型的三維偏序問題。直接樹套樹帶走。

提交記錄

  • Levko and Array

樹套樹解決二維數點問題

這里的二維數點定義比較廣泛,包括點個數的計數,以及滿足某些性質的點集查詢等。

通常,二維數點問題有以下幾種做法:

  • 將第一維分塊,塊內套 set 等數據結構維護第二維。同時對于每個 \(x\) 坐標建立一個 set,用于維護散塊信息。

  • 對第一維建線段樹,線段樹節點里面套 set / 平衡樹。

  • 對第一維建樹狀數組,樹狀數組每個節點里套 set / 平衡樹。

對于第一種方法,直接對于整塊 / 散塊里的平衡樹 lower_bound 即可。

對于第二種方法,定位到線段樹上的 \(O(\log n)\) 個區間之后和第一種一樣。

對于第三種方法,需要根據情況具體分析。有時需要維護樹狀數組后綴 \(\max\) 或者前綴 \(\max\),有時需要維護點數等等。優勢是常數小。

CF19D Points

很好的一道題。但是由于題目喪心病狂的卡常,我至今沒有用樹狀數組套 set 卡過去。

題目顯然是二維數點,屬于求滿足某種條件的點集類型題目。條件是某個點右上方的最左下的點。

第一種思路是分塊套 set。對于橫縱坐標離散化之后,對于每個橫坐標維護一個 set,記錄橫坐標為該值的所有縱坐標。同時對每個塊維護一個類型為 pairset,維護橫坐標在該塊內的所有點。

每次插入和查詢復雜度都是 \(O(\log n)\)。查詢復雜度需要查詢 \(O(\sqrt n)\) 個整塊,每個整塊需要 \(O(\log n)\) 的復雜度。需要查詢 \(O(\sqrt n)\) 個單點,單點復雜度 \(O(\log n)\)。因此總復雜度 \(O(m \sqrt n \log n)\)。輕松的跑過去了。

第二種思路是樹狀數組套 set。對于縱坐標用樹狀數組維護,內層套 set 維護橫坐標。插入的時候只需要在右端點 \(< y\) 的節點的 set 內插點就可以了。對于查詢操作,只需要在左端點 \(\ge y\) 的節點 setlower_bound 即可。復雜度 \(O(m \log ^ 2 n)\)。

不知道為什么,常數和復雜度都小的樹狀數組套 set 沒有卡過去/kk

分塊套 set 代碼

樹狀數組套 set :

set<PII> s[N]; vector<int> p;
int n, lim;
struct Q { int op, x, y; }q[N];
void add(int x, int y) {
	for (int i = y; i; i -= (i & -i)) s[i].insert(mp(x, y));
}
void del(int x, int y) {
	for (int i = y; i; i -= (i & -i)) s[i].erase(mp(x, y));
}
PII ask(int x, int y) {
	PII ans = mp(INF, INF);
	for (int i = y; i <= lim; i += (i & -i))
		ans = min(ans, *s[i].lower_bound(mp(x, y)));
	return ans;
}
int main() {
	read(n);
	rep(i, 1, n) {
		char ch[7]; int x, y;
		scanf("%s", ch); read(x, y);
		if (*ch == 'a') q[i] = {0, x, y};
		if (*ch == 'r') q[i] = {1, x, y};
		if (*ch == 'f') q[i] = {2, ++ x, ++ y};
		p.push_back(y);
	} sort(all(p)); p.resize(unique(all(p)) - p.begin()); 
	lim = p.size();
	auto find = [](int x) -> int {
		return lower_bound(all(p), x) - p.begin() + 1;
	};
	rep(i, 1, n) q[i].y = find(q[i].y);
	rep(i, 1, lim) s[i].insert(mp(INF, INF));
	rep(i, 1, n) {
		if (q[i].op == 0) add(q[i].x, q[i].y);
		if (q[i].op == 1) del(q[i].x, q[i].y);
		if (q[i].op == 2) {
			register PII ans = ask(q[i].x, q[i].y);
			if (ans.first == INF) puts("-1");
			else write(' ', ans.first, p[ans.second - 1]), pc('\n');
		}
	} return 0;
}

Intersection of Permutations

比較套路的一道題。若有 \(b_x = a_y\),那么記 \(t_x = y\)

問題轉化成了:

  • 查詢操作:查詢在 \(l_b \sim r_b\) 中,有多少 \(t_i \in [l_a, r_a] \bigcup \mathbb{Z}\)。

  • 修改操作:交換 \(t_x, t_y\)。

這是一個帶修的二維數點,直接樹狀數組套權值線段樹帶走。

注意要寫空間回收,要不然會 MLE。

struct node { int ls, rs, s; }tr[M];
#define ls tr[u].ls
#define rs tr[u].rs
#define mid (l + r >> 1)
int New() { return !top ? ++ cnt : stk[top -- ]; }
void add(int &u, int l, int r, int x, int v) {
	if (l > x or r < x) return;
	if (!u) u = New(); tr[u].s += v; if (l == r) return;
	add(ls, l, mid, x, v), add(rs, mid + 1, r, x, v);
	if (!tr[u].s) stk[ ++ top] = u, u = 0;
}
void ADD(int x, int p, int v) {
	for (int i = x; i <= n; i += (i & -i)) add(rt[i], 1, n, p, v);
}
int ask(int u, int l, int r, int L, int R) {
	if (!u or r < L or l > R) return 0;
	if (l >= L and r <= R) return tr[u].s;
	return ask(ls, l, mid, L, R) + ask(rs, mid + 1, r, L, R);
}
int ASK(int la, int ra, int lb, int rb, int s = 0) { lb -- ;
	for (int i = rb; i; i -= (i & -i)) s += ask(rt[i], 1, n, la, ra);
	for (int i = lb; i; i -= (i & -i)) s -= ask(rt[i], 1, n, la, ra); return s;
}
int main() {
	scanf("%d%d", &n, &m);
	rep(i, 1, n) scanf("%d", &a[i]), bin[a[i]] = i;
	rep(i, 1, n) scanf("%d", &b[i]);
	rep(i, 1, n) t[i] = bin[b[i]], ADD(i, t[i], 1);
	while (m -- ) {
		int op, la, ra, lb, rb, x, y;
		scanf("%d", &op);
		if (op & 1) {
			scanf("%d%d%d%d", &la, &ra, &lb, &rb);
			printf("%d\n", ASK(la, ra, lb, rb));
		} else {
			scanf("%d%d", &x, &y);
			ADD(x, t[x], -1); ADD(y, t[y], -1);
			swap(t[x], t[y]); ADD(x, t[x], 1); ADD(y, t[y], 1);
		}
	} return 0;
}

樹套樹解決帶修區間第 \(k\) 大問題

不帶修的區間第 \(k\) 大,正經解法就是主席樹。

那么如果帶修了呢?

可以外層一個樹狀數組維護下標,內層一個權值線段樹維護這個區間的權值。

這樣相當于把一棵主席樹拆成了許多個動態開點線段樹。

修改時,每次修改樹狀數組的 \(\log\) 個節點,每修改一個節點需要一個老哥,所以就是 \(O(\log ^ 2 n)\)。

查詢就是前綴和的容斥。前綴和要在樹狀數組上做,所以是兩個 \(\log\)。

經典例題:

P2617 Dynamic Rankings

放一下主體部分代碼:

struct node {
	int ls, rs, s;
}tr[M]; int rt[N];
vector<int> L, R;
int n, m, cnt, a[N];
#define lc tr[u].ls
#define rc tr[u].rs
#define mid (l + r >> 1)
void add(int &u, int l, int r, int x, int v) {
	if (l > x or r < x) return;
	if (!u) u = ++ cnt; tr[u].s += v;
	if (l == r) return;
	add(lc, l, mid, x, v); add(rc, mid + 1, r, x, v);
}
void ADD(int x, int v, int c) {
	for (int i = x; i <= n; i += (i & -i))
		add(rt[i], 0, V, v, c);
}
int ask(int l, int r, int k) {
	if (l == r) return r; int s = 0;
	for (auto &u : R) s += tr[lc].s;
	for (auto &u : L) s -= tr[lc].s;
	if (k <= s) {
		for (auto &u : L) u = lc;
		for (auto &u : R) u = lc;
		return ask(l, mid, k);
	}
	for (auto &u : L) u = rc;
	for (auto &u : R) u = rc;
	return ask(mid + 1, r, k - s);
}
int ASK(int l, int r, int k) {
	L.clear(); R.clear(); l -- ;
	for (int i = r; i; i -= (i & -i)) R.pb(rt[i]);
	for (int i = l; i; i -= (i & -i)) L.pb(rt[i]);
	return ask(0, V, k);
}

Summary: 在大部分時候,需要維護的信息具有左 / 右端點固定或者可差分性時,使用樹狀數組套數據結構是一個不錯的選擇。

當然,有時分塊套數據結構可以獲得意想不到的小常數。

總結

以上是生活随笔為你收集整理的高级数据结构笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。