线段树做题笔记

\(\color{#3498D8}(1)\) P4145 上帝造题的七分钟 2 / 花神游历各国

  • 区间开根,区间求和。

注意到对于一个 \(10^{12}\) 的数而言,最多开根 \(6\) 次就会变成 \(1\)。接下来在开根都不会发生变化了。

所以修改时我们可以维护区间最大值。如果区间最大值小于等于 \(1\),那么直接将这个区间跳过。否则暴力递归处理两个儿子。

$\color{blue}\text{Code}$
#include <iostream>
#include <cmath> 

using namespace std;

#define int long long

const int N = 1000010;

int n, m, a[N], k, l, r;

#define ls (u << 1)
#define rs (u << 1 | 1) 

struct Tree
{
	int l, r, v, mx;
}tr[N << 2];

void pushup(int u)
{
	tr[u].v = tr[ls].v + tr[rs].v;
	tr[u].mx = max(tr[ls].mx, tr[rs].mx);
}

void build(int u, int l, int r)
{
	tr[u] = {l, r};
	if (l == r) tr[u].v = tr[u].mx = a[l];
	else
	{
		int mid = l + r >> 1;
		build(ls, l, mid), build(rs, mid + 1, r);
		pushup(u);
	}
}

void modify(int u, int l, int r)
{
	if (tr[u].l == tr[u].r) tr[u].v = tr[u].mx = sqrt(tr[u].v);
	else
	{
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid && tr[ls].mx != 1) modify(ls, l, r);
		if (r > mid && tr[rs].mx != 1) modify(rs, l, r);
		pushup(u);
	}
}

int query(int u, int l, int r)
{
	if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
	int mid = tr[u].l + tr[u].r >> 1, res = 0;
	if (l <= mid) res = query(ls, l, r);
	if (r > mid) res += query(rs, l, r);
	return res;
}

main()
{
	cin >> n;
	
	for (int i = 1; i <= n; ++ i ) cin >> a[i];
	
	build(1, 1, n);
	
	cin >> m;
	
	while (m -- )
	{
		cin >> k >> l >> r;
		if (l > r) swap(l, r);
		if (k) cout << query(1, l, r) << '\n';
		else modify(1, l, r);
	}
	
	return 0;
}

\(\color{#BFBFBF}(2)\) UOJ 228 基础数据结构练习题

  • 维护操作:
    • \(\forall i \in [l, r], a_i \gets a_i + k\)
    • \(\forall i \in [l, r], a_i \gets \lfloor \sqrt{a_i} \rfloor\)
    • 查询 \(\sum_{i=l}^r a_i\)

对于一个区间 \([l, r]\),如果将这个区间全部开根,那么极差一定也会至少开根。

如果令原最大值最小值为 \(x, y\),那么可以推出 \(\sqrt x - \sqrt y \le \sqrt{x - y}\)。所以我们要在极差上做处理。

当我们需要对一个线段树上的区间节点 \([l, r]\) 且最大最小值为 \(x, y\) 做开根时,如果 \(x - y = \lfloor \sqrt x \rfloor - \lfloor \sqrt y \rfloor\),那么开根操作可以转化为减 \(x - \lfloor \sqrt x \rfloor\)。否则,暴力递归到左右儿子分别处理。

所以维护区间最大值、最小值、和。可以证明这样做的复杂度是正确的。

注意开根下取整。

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 100010;

int n, m, a[N], op, l, r, x;

struct Tree {
	int l, r, v, add, mx, mn;
}tr[N << 2];

void pushup(int u) {
	tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v;
	tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
	tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
	return;
}

void calc(int u, int d) {
	tr[u].add += d;
	tr[u].v += (tr[u].r - tr[u].l + 1) * d;
	tr[u].mn += d;
	tr[u].mx += d;
	return;
}

void pushdown(int u) {
	calc(u << 1, tr[u].add);
	calc(u << 1 | 1, tr[u].add);
	tr[u].add = 0;
	return;
}

void build(int u, int l, int r) {
	tr[u] = {l, r};
	if (l == r) tr[u].v = tr[u].mx = tr[u].mn = a[l];
	else {
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	return;
}

void add(int u, int l, int r, int x) {
	if (tr[u].l >= l && tr[u].r <= r) calc(u, x);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		pushdown(u);
		if (l <= mid) add(u << 1, l, r, x);
		if (r > mid) add(u << 1 | 1, l, r, x);
		pushup(u);
	}
	return;
}

int F(int x) {
	return sqrt(x);
}

void modify(int u, int l, int r) {
	if (tr[u].l == tr[u].r) {
		tr[u].mn = tr[u].mx = tr[u].v = sqrt(tr[u].v);
		tr[u].add = 0;
	}
	else if (tr[u].l >= l && tr[u].r <= r && tr[u].mx - F(tr[u].mx) == tr[u].mn - F(tr[u].mn)) {
		calc(u, F(tr[u].mx) - tr[u].mx);
	}
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		pushdown(u);
		if (l <= mid) modify(u << 1, l, r);
		if (r > mid) modify(u << 1 | 1, l, r);
		pushup(u);
	}
	return;
}

int query(int u, int l, int r) {
	if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
	int mid = tr[u].l + tr[u].r >> 1, res = 0;
	pushdown(u);
	if (l <= mid) res = query(u << 1, l, r);
	if (r > mid) res += query(u << 1 | 1, l, r);
	pushup(u);
	return res;
} 

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++ i ) {
		scanf("%lld", a + i);
	}
	
	build(1, 1, n);
	
	while (m -- ) {
		scanf("%lld%lld%lld", &op, &l, &r);
		if (op == 1) {
			scanf("%lld", &x);
			add(1, l, r, x);
		}
		else if (op == 2) {
			modify(1, l, r);
		}
		else {
			printf("%lld\n", query(1, l, r));
		}
	}
	
	return 0;
} 

\(\color{#BFBFBF}(3)\) LOJ 6029 市场

维护操作:

  • \(\forall i \in [l, r], a_i \gets a_i + k\)
  • \(\forall i \in [l, r], a_i \gets \lfloor \frac{a_i}d \rfloor\)
  • 查询 \(\min_{i=l}^r a_i\)
  • 查询 \(\sum_{i=l}^r a_i\)

同上题思路。注意负数下取整。

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 100010;

int n, m, a[N], op, l, r, x;

struct Tree {
	int l, r, v, add, mx, mn;
}tr[N << 2];

void pushup(int u) {
	tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v;
	tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
	tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
	return;
}

void calc(int u, int d) {
	tr[u].add += d;
	tr[u].v += (tr[u].r - tr[u].l + 1) * d;
	tr[u].mn += d;
	tr[u].mx += d;
	return;
}

void pushdown(int u) {
	calc(u << 1, tr[u].add);
	calc(u << 1 | 1, tr[u].add);
	tr[u].add = 0;
	return;
}

void build(int u, int l, int r) {
	tr[u] = {l, r};
	if (l == r) tr[u].v = tr[u].mx = tr[u].mn = a[l];
	else {
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	return;
}

void add(int u, int l, int r, int x) {
	if (tr[u].l >= l && tr[u].r <= r) calc(u, x);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		pushdown(u);
		if (l <= mid) add(u << 1, l, r, x);
		if (r > mid) add(u << 1 | 1, l, r, x);
		pushup(u);
	}
	return;
}

int F(int x, int y) {
	return floor(1.0 * x / y);
}

void modify(int u, int l, int r, int x) {
	if (tr[u].l == tr[u].r) {
		tr[u].mn = tr[u].mx = tr[u].v = F(tr[u].v, x);
	}
	else if (tr[u].l >= l && tr[u].r <= r && tr[u].mx - F(tr[u].mx, x) == tr[u].mn - F(tr[u].mn, x)) {
		calc(u, F(tr[u].mx, x) - tr[u].mx);
	}
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		pushdown(u);
		if (l <= mid) modify(u << 1, l, r, x);
		if (r > mid) modify(u << 1 | 1, l, r, x);
		pushup(u);
	}
	return;
}

int sum(int u, int l, int r) {
	if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
	int mid = tr[u].l + tr[u].r >> 1, res = 0;
	pushdown(u);
	if (l <= mid) res = sum(u << 1, l, r);
	if (r > mid) res += sum(u << 1 | 1, l, r);
	pushup(u);
	return res;
} 

int minn(int u, int l, int r) {
	if (tr[u].l >= l && tr[u].r <= r) return tr[u].mn;
	int mid = tr[u].l + tr[u].r >> 1, res = 1e18;
	pushdown(u);
	if (l <= mid) res = minn(u << 1, l, r);
	if (r > mid) res = min(res, minn(u << 1 | 1, l, r));
	pushup(u);
	return res;
} 

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++ i ) {
		scanf("%lld", a + i);
	}
	
	build(1, 1, n);
	
	while (m -- ) {	
		scanf("%lld%lld%lld", &op, &l, &r);
		++ l, ++ r;
		if (op == 1) {
			scanf("%lld", &x);
			add(1, l, r, x);
		}
		else if (op == 2) {
			scanf("%lld", &x);
			if (x != 1) modify(1, l, r, x);
		}
		else if (op == 3) {
			printf("%lld\n", minn(1, l, r));
		}
		else {
			printf("%lld\n", sum(1, l, r));
		}
	}
	
	return 0;
} 

\(\color{#3498D8}(4)\) CF935E Fafa and Ancient Mathematics

  • 给出一个算式,由括号和小于 \(10\) 的正整数和问号组成。每个问号可以改为加号或减号。

    求如果总共填 \(p\) 个加号和 \(m\) 个减号,求算式的最大值。

  • \(n \le 10^4\)\(\min(p, m) \le 100\)

建表达式树。如 \(((1 + 2) - ((3 - 4) + 5))\)

然后设 DP 状态 \(f_{u, k}\)\(g_{u, k}\) 分别表示以 \(u\) 为根的子树中,如果用 \(k\) 个加号/减号,代数式的最大值是多少。具体是加号/减号可以看 \(p, m\) 谁更小,因为题目保证 \(\min(p, m) \le 100\) 所以这样设状态是可行的。

转移时枚举 \(u\) 选择加号/减号,左子树用了几个加号/减号,那么右子树的加号/减号数量是可以被计算的。然后转移即可。

$\color{blue}\text{Code}$
const int N = 20010;

char s[N];
int n, a, b;

struct Tree {
	int l, r, v;
}tr[N];

int idx;
int l[N], r[N];

long long f[N][110], g[N][110];
map<int, vector<int> > pos; 
int sum[N];
int que[N];

inline int build(const int ll, const int rr) {
	const int u = ++ idx;
	tr[u] = {ll, rr};
	
	if (ll == rr) f[u][0] = g[u][0] = s[ll] - '0';
	else {
		register int x = *lower_bound(pos[sum[ll - 1] + 1].begin(), pos[sum[ll - 1] + 1].end(), ll);
		tr[u].v = que[rr] - que[ll - 1];
		l[u] = build(ll + 1, x - 1);
		r[u] = build(x + 1, rr - 1);
	}
	
	return u;
}

inline void dfsa(const int u) {
	if (tr[u].l == tr[u].r) return;
	
	dfsa(l[u]), dfsa(r[u]);
	for (register int i = 0; i <= tr[u].v; ++ i )
		for (register int j = 0; j <= tr[l[u]].v; ++ j ) {
			if (i - j - 1 <= tr[r[u]].v && i - j - 1 >= 0)
				f[u][i] = max(f[u][i], f[l[u]][j] + f[r[u]][i - j - 1]),
				g[u][i] = min(g[u][i], g[l[u]][j] + g[r[u]][i - j - 1]);
			
			if (i - j <= tr[r[u]].v && i - j >= 0)
				f[u][i] = max(f[u][i], f[l[u]][j] - g[r[u]][i - j]),
				g[u][i] = min(g[u][i], g[l[u]][j] - f[r[u]][i - j]);
		}
	
	return;
}

inline void dfsb(const int u) {
	if (tr[u].l == tr[u].r) return;
	
	dfsb(l[u]), dfsb(r[u]);
	for (register int i = 0; i <= tr[u].v; ++ i )
		for (register int j = 0; j <= tr[l[u]].v; ++ j ) {
			if (i - j - 1 <= tr[r[u]].v && i - j - 1 >= 0)
				f[u][i] = max(f[u][i], f[l[u]][j] - g[r[u]][i - j - 1]),
				g[u][i] = min(g[u][i], g[l[u]][j] - f[r[u]][i - j - 1]);
			
			if (i - j <= tr[r[u]].v && i - j >= 0)
				f[u][i] = max(f[u][i], f[l[u]][j] + f[r[u]][i - j]),
				g[u][i] = min(g[u][i], g[l[u]][j] + g[r[u]][i - j]);
		}
	
	return;
}

signed main() {
	memset(f, -0x3f, sizeof f);
	memset(g, 0x3f, sizeof g);
	scanf("%s%d%d", s + 1, &a, &b);
	n = strlen(s + 1);
	
	pos[0].push_back(0); 
	for (int i = 1; i <= n; ++ i ) {
		sum[i] = sum[i - 1];
		sum[i] += s[i] == '(';
		sum[i] -= s[i] == ')';
		if (s[i] == '?') pos[sum[i]].push_back(i);
		que[i] = que[i - 1] + (s[i] == '?');
	}
	
	build(1, n);
	if (a <= b) {
		dfsa(1);
		printf("%lld\n", f[1][a]);
	}
	else {
		dfsb(1);
		printf("%lld\n", f[1][b]);
	}
	
	return 0;
}

\(\color{#9D3DCF}(5)\) CF786B Legacy

  • 有一张 \(n\) 个节点和若干条边。边用 \(q\) 条信息表示:

    • 1 v u w 表示有一条连接 \(v \to u\) 的有向边,边权为 \(w\)
    • 2 v l r w 表示对于所有 \(u \in [l, r]\),都有一条连接 \(v \to u\) 的有向边,边权为 \(w\)
    • 3 v l r w 表示对于所有 \(u \in [l, r]\),都有一条连接 \(u \to v\) 的有向边,边权为 \(w\)

    \(s\) 到每个点的最短路。

线段树优化建图。

建两颗线段树“入树”和“出树”,每次连边时将入树的线段树节点连向出树的线段树节点。

同时,在入树中,我们连边儿子 \(\to\) 父亲边权为 \(0\)。在出树中连边父亲 \(\to\) 儿子边权为 \(0\)

又因为两个数中每个叶子节点本质是一样的,所以在相同的叶子节点间连接边权为 \(0\) 的双向边。

最后从某棵树的叶子节点 \(s\) 跑最短路即可。

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 1000010;

int n, q, s;
vector<pair<int, int> > g[N];
int root[2];

void add(int a, int b, int w, bool flg = 0) {
	g[a].push_back({b, w});
	if (flg) add(b, a, w);
	return;
}

struct Tree {
	int l, r;		// 区间
	int ls, rs;	// 左右儿子 
	bool flg;		// 哪棵树 
}tr[N];

int idx;
map<pair<int, bool>, int> Id;

int build(int l, int r, bool flg) {
	int u = ++ idx;
	tr[u].l = l, tr[u].r = r, tr[u].flg = flg;
	if (l != r) {
		int mid = l + r >> 1;
		tr[u].ls = build(l, mid, flg);
		tr[u].rs = build(mid + 1, r, flg);
		if (!flg) add(tr[u].ls, u, 0), add(tr[u].rs, u, 0);
		else add(u, tr[u].ls, 0), add(u, tr[u].rs, 0); 
	}
	else Id[{l, flg}] = u;
	return u;
}

int dis[N];
bool st[N];

void Dijkstra(int s) {
	priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
	q.push({0, s});
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0;
	while (q.size()) {
		int u = q.top().second;
		q.pop();
		if (st[u]) continue;
		st[u] = true;
		for (auto t : g[u]) {
			int v = t.first, w = t.second;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				q.push({dis[v], v});
			}
		}
	}
	return;
}

void u_to_seg(int u, int l, int r, int to, int w) {
	if (tr[u].l >= l && tr[u].r <= r) add(to, u, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) u_to_seg(tr[u].ls, l, r, to, w);
		if (r > mid)  u_to_seg(tr[u].rs, l, r, to, w);
	}
	return;
}

void seg_to_v(int u, int l, int r, int to, int w) {
	if (tr[u].l >= l && tr[u].r <= r) add(u, to, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) seg_to_v(tr[u].ls, l, r, to, w);
		if (r > mid)  seg_to_v(tr[u].rs, l, r, to, w);
	}
	return;
}

signed main() {
	cin >> n >> q >> s;
	
	root[0] = build(1, n, 0);
	root[1] = build(1, n, 1);
	for (int i = 1; i <= n; ++ i ) {
		add(Id[{i, 0}], Id[{i, 1}], 0, 1);
	}
	
	while (q -- ) {
		int op, v, u, l, r, w;
		cin >> op;
		if (op == 1) {
			cin >> v >> u >> w;
			add(Id[{v, 0}], Id[{u, 1}], w);
		}
		else if (op == 2) {
			cin >> u >> l >> r >> w;
			u_to_seg(root[1], l, r, Id[{u, 0}], w);
		}
		else {
			cin >> v >> l >> r >> w;
			seg_to_v(root[0], l, r, Id[{v, 1}], w);
		}
	}
	
	Dijkstra(Id[{s, 0}]);
	for (int i = 1; i <= n; ++ i ) {
		int res = dis[Id[{i, 0}]];
		if (res > 1e15) res = -1;
		cout << res << ' ';
	}
	
	return 0;
}

\(\color{#9D3DCF}(6)\) P6348 Journeys

  • 有一张 \(n\) 个节点和若干条边。边用 \(m\) 条信息表示:

    • l1 r1 l2 r2 表示对于所有 \(u \in [l1, r1], v \in [l2, r2]\),都有一条双向边连接 \(u, v\)

    \(P\) 到每个点的最短路。

与上题类似。对于每条信息 l1 r1 l2 r2,我们建立一个虚点,并将 \(l1 \sim r_1\)\(l2 \sim r2\) 向这个虚点连接边权为 \(\frac 12\) 的双向边。这里仍然是线段树优化。

然后跑最短路即可。

实际上,在向虚点连边时可以连接边权为 \(1\) 的边,最终答案全部除以二。

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

const int N = 5000010;

int n, q, s;
vector<pair<int, double> > g[N];
int root[2];

void add(int a, int b, double w) {
	g[a].push_back({b, w});
	return;
}

struct Tree {
	int l, r;		// 区间
	int ls, rs;	// 左右儿子 
	bool flg;		// 哪棵树 
}tr[N];

int idx;
map<pair<int, bool>, int> Id;

int build(int l, int r, bool flg) {
	int u = ++ idx;
	tr[u].l = l, tr[u].r = r, tr[u].flg = flg;
	if (l != r) {
		int mid = l + r >> 1;
		tr[u].ls = build(l, mid, flg);
		tr[u].rs = build(mid + 1, r, flg);
		if (!flg) add(tr[u].ls, u, 0), add(tr[u].rs, u, 0);
		else add(u, tr[u].ls, 0), add(u, tr[u].rs, 0); 
	}
	else Id[{l, flg}] = u;
	return u;
}

double dis[N];
bool st[N];

void Dijkstra(int s) {
	priority_queue<pair<double, int>, vector<pair<double, int> >, greater<pair<double, int> > > q;
	q.push({0, s});
	for (int i = 1; i <= idx; ++ i ) dis[i] = 1e18; 
	dis[s] = 0;
	while (q.size()) {
		int u = q.top().second;
		q.pop();
		if (st[u]) continue;
		st[u] = true;
		for (auto t : g[u]) {
			int v = t.first;
			double w = t.second;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				q.push({dis[v], v});
			}
		}
	}
	return;
}

void u_to_seg(int u, int l, int r, int to, double w) {
	if (tr[u].l >= l && tr[u].r <= r) add(to, u, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) u_to_seg(tr[u].ls, l, r, to, w);
		if (r > mid)  u_to_seg(tr[u].rs, l, r, to, w);
	}
	return;
}

void seg_to_v(int u, int l, int r, int to, double w) {
	if (tr[u].l >= l && tr[u].r <= r) add(u, to, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) seg_to_v(tr[u].ls, l, r, to, w);
		if (r > mid)  seg_to_v(tr[u].rs, l, r, to, w);
	}
	return;
}

signed main() {
	cin >> n >> q >> s;
	
	root[0] = build(1, n, 0);
	root[1] = build(1, n, 1);
	for (int i = 1; i <= n; ++ i ) {
		add(Id[{i, 0}], Id[{i, 1}], 0);
		add(Id[{i, 1}], Id[{i, 0}], 0);
	}
	
	while (q -- ) {
		int l1, r1, l2, r2;
		cin >> l1 >> r1 >> l2 >> r2;
		
		seg_to_v(root[0], l1, r1, ++ idx, 0.5);
		u_to_seg(root[1], l2, r2, idx, 0.5);
		seg_to_v(root[0], l2, r2, ++ idx, 0.5);
		u_to_seg(root[1], l1, r1, idx, 0.5);
	}
	
	Dijkstra(Id[{s, 0}]);
	for (int i = 1; i <= n; ++ i ) {
		cout << dis[Id[{i, 0}]] << '\n';
	}
	
	return 0;
}

\((7)\) 区间 chkmax 公差一定的等差数列

  • 给定一个整数 \(d\) 和一个初始全为 \(0\) 的长度为 \(n\) 的序列 \(a\)\(m\) 次修改给定 \(l, r, x\)
    • 令序列 \(b_l = x, b_{l+1} = x + d, b_{l+2} = x + 2d, \dots, b_r = x + d(r-l)\)。将所有 \(i \in [l, r]\) 执行 \(a_i \gets \max(a_i, b_i)\)
  • 输出最终的 \(a\)

发现 \(b\) 就是一个公差为 \(d\) 的等差数列。即操作为:

对于所有 \(i \in [l, r]\) 执行 \(a_i \gets \max(a_i, x + d(i-l))\)

即:

对于所有 \(i \in [l, r]\) 执行 \(a_i \gets \max(a_i, x - dl + di)\)

发现只要存在一个覆盖 \(i\) 的操作,它就一定会获得 \(di\) 的贡献。不妨将这个贡献最后计算。每次只操作:

对于所有 \(i \in [l, r]\) 执行 \(a_i \gets \max(a_i, x - dl)\)

其中 \(x - dl\) 是一个定值。此时就可以用一般线段树维护了。

\(\color{#9D6DCF}(8)\) P4137 Rmq Problem / mex

  • 静态区间求 \(\operatorname{mex}\)

对于区间 \([l, r]\) 而言,根据定义,这个区间的 \(\operatorname{mex}\) 为最小的未出现过的数。

如果 \(\operatorname{mex}_{[l, r]} = x\),那么 \(1 \sim r\)\(x\) 要么没出现过,要么最后一次出现的位置在 \(l\) 之前。

如果固定 \(r\),我们考虑维护值域线段树。现在这颗线段树上的每个节点的值,存储的是 \(1 \sim r\) 中这个值的最后一次出现位置,未出现则为 \(0\)。例如,对于 \(a = \{1, 1, 4, 5, 1, 4\}\),那么这颗线段树就应该是 \(\{5, 0, 0, 6, 4\}\)

然后对于某个询问 \([l, r]\) 而言,我们需要找到这颗线段树上最靠前的一个值,且这个值 \(< l\)。线段树维护区间最小值并线段树二分即可。

\(r\) 不固定,则可将询问离线,按照右端点排序,然后从左往右动态更新这颗值域线段树即可。

posted @ 2024-03-27 17:13  2huk  阅读(18)  评论(0编辑  收藏  举报