1. 程式人生 > 其它 >求區間mex——離線線段樹做法

求區間mex——離線線段樹做法

區間mex

最近做牛客上的題(little w and Discretization)的時候,遇到了這樣一種需求,給定一個序列,然後再給出一系列查詢,求序列中,\(l\)\(r\)之間的數的mex(簡單介紹:mex(S) 的值為集合 S 中沒有出現過的最小自然數)。

花了點時間做出這題之後,稍微瞭解了一些關於求區間mex的線段樹離線寫法,姑且做一個筆記


模板問題

P4137 Rmq Problem / mex - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)

核心思想

用線段樹維護一個序列,序列中儲存該下標在給出的序列中最後一次出現的位置,如t[x] = pos,意味\(x\)

在原序列中最後一次出現的下標為\(pos\)

線段樹維護一個區間最小值,線段樹的點的含義是該數最後一次出現的下標,線段樹區間的含義是這一段中最小的最後一次出現的下標。

查詢前,我們讀入所有的查詢的\(l,r\),將它們按右端點排序。從左到右遍歷整個給出的區間,更新每一個樹的最後出現的位置(使用線段樹的單點修改)。當位置達到當前區間的右邊界時,線上段樹上進行二分(這就是為什麼線段樹要維護區間最小的原因),如果位置小於左端點,說明在所詢問的段中這個數沒有出現過,是可行的值。最後找到mex。

更多細節可以配合程式碼理解

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 1000;
int n, m;
struct Segtree {
	int t[(maxn << 2) + 10];
	void init() {
		for (int i = 1; i <= maxn; i++)
			t[i] = -1;
	}
	void update(int val, int pos, int l = 1, int r = n + 1, int rt = 1) {
		if (l == r) {
			t[rt] = pos;
			return;
		}
		int mid = (l + r) / 2;
		if (val <= mid)
			update(val, pos, l, mid, rt << 1);
		else if (val > mid)
			update(val, pos, mid + 1, r, rt << 1 | 1);
		t[rt] = min(t[rt << 1], t[rt << 1 | 1]);
	}
	int query(int pos, int l = 1, int r = n + 1, int rt = 1) {//l,r是二分的左右端點
		if (l == r)
			return l;
		int mid = (l + r) / 2;
		if (t[rt << 1] < pos)					//如果這一段中有一個數的下標最後一次出現在左區間左端說明該值可行,往更小的數繼續找
			return query(pos, l, mid, rt << 1);
		else
			return query(pos, mid + 1, r, rt << 1 | 1);
	}
};
int a[maxn + 10];
struct Ask {
	int l, r;
	int id;
	int mex;
};

Segtree t1;
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		a[i]++;				//因為線段樹要處理0會很麻煩,不如都從1開始記,最後再減去1
	}
	t1.init();
	vector<Ask> v(m + 10);
	for (int i = 1; i <= m; i++) {
		cin >> v[i].l >> v[i].r;
		v[i].id = i;
	}

	sort(v.begin() + 1, v.begin() + 1 + m, [](Ask a, Ask b) {return a.r < b.r; });//按右端點排序

	for (int i = 1, p = 1; i <= n; i++) {
		if (a[i] <= n) {
			t1.update(a[i], i);				//更新每一個數最後出現的位置
		}
		while (p <= m && v[p].r == i) {
			v[p].mex = t1.query(v[p].l);
			p++;
		}
	}

	sort(v.begin() + 1, v.begin() + 1 + m, [](Ask a, Ask b) {return a.id < b.id; });//還原順序
	for (int i = 1; i <= m; i++) {
		cout << v[i].mex - 1 << "\n";
	}
	return;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	solve();
	return 0;
}