P4137 Rmq Problem / mex (三种解法: 权值线段树+询问离线 / 可持久化线段树 / 莫队+值域分块)

题目链接: P4137 Rmq Problem / mex

大致题意

给定一个长度为 n n n的序列, 有 m m m次询问操作.

每次询问 [ l , r ] [l, r] [l,r]区间的 m e x mex mex是多少.

解题思路

解法一: 权值线段树 + 询问离线

我们考虑把所有的询问按照右端点 r r r从小到大排序, 用权值线段树维护 [ 1 , r ] [1, r] [1,r]区间中所有数值最后一次出现位置的最小值.

对于询问区间 [ l , r ] [l, r] [l,r], 我们在树上进行二分, 如果当前值域左区间的最小出现位置要小于 l l l, 则访问左区间. 反之访问右区间.

对于没有出现过的值, 树中会认为出现在了 0 0 0位置, 是不会影响答案正确性的.


解法二: 可持久化权值线段树

同解法一, 但可以通过可持久化的方式, 转化为在线做法.


解法三: 莫队 + 值域分块

我们同样可以考虑用莫队去维护.

考虑到维护 [ l , r ] [l, r] [l,r]信息时, 由于我们需要求 m e x mex mex, 我们区间扩增和收缩时, 都不能 O ( 1 ) O(1) O(1)维护出当前区间的答案.

于是我们考虑对值域进行分块, 块内维护当前值域数值出现过的个数(去重后).

这样我们可以在 O ( n ) O(\sqrt{n}) O(n )的时间内查询出区间答案. 在 O ( n ) O(\sqrt{n}) O(n )的时间内维护区间信息.

AC代码

权值线段树

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, L = 0, R = N - 5;
int w[N];
struct node {
    
    
	int l, r;
	int val;
}t[N << 2];
void pushup(int x) {
    
     t[x].val = min(t[x << 1].val, t[x << 1 | 1].val); }
void build(int l, int r, int x = 1) {
    
    
	t[x] = {
    
     l, r, 0 };
	if (l == r) return;
	int mid = l + r >> 1;
	build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}

void modify(int a, int c, int x = 1) {
    
    
	if (t[x].l == t[x].r) {
    
    
		t[x].val = c;
		return;
	}
	int mid = t[x].l + t[x].r >> 1;
	modify(a, c, x << 1 | (a > mid));
	pushup(x);
}

int ask(int pos, int x = 1) {
    
    
	if (t[x].l == t[x].r) return t[x].l;
	if (t[x << 1].val < pos) return ask(pos, x << 1);
	return ask(pos, x << 1 | 1);
}


struct query {
    
    
	int l, r, id;
	bool operator< (const query& t) const {
    
     return r < t.r; }
}; vector<query> area;
int res[N];
int main()
{
    
    
	int n, m; cin >> n >> m;
	build(L, R);
	rep(i, n) scanf("%d", &w[i]);

	rep(i, m) {
    
    
		int l, r; scanf("%d %d", &l, &r);
		area.push_back({
    
     l, r, i });
	}
	sort(area.begin(), area.end());

	int pos = 0;
	for (auto& [l, r, id] : area) {
    
    
		while (pos + 1 <= r) {
    
    
			++pos;
			modify(w[pos], pos);
		}
		res[id] = ask(l);
	}

	rep(i, m) printf("%d\n", res[i]);

    return 0;
}

可持久化线段树

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, L = 0, R = N - 5;
struct node {
    
    
	int l, r;
	int val;
}t[N * 20];
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) {
    
    
	int x = ++ind; t[x] = t[p];
	if (tl == tr) {
    
    
		t[x].val = c;
		return x;
	}
	int mid = tl + tr >> 1;
	if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
	else t[x].r = build(a, c, mid + 1, tr, t[p].r);
	t[x].val = min(t[t[x].l].val, t[t[x].r].val);
	return x;
}
int ask(int tl, int tr, int pos, int x) {
    
    
	if (tl == tr) return tl;
	int mid = tl + tr >> 1;
	if (t[t[x].l].val < pos) return ask(tl, mid, pos, t[x].l);
	return ask(mid + 1, tr, pos, t[x].r);
}
int main()
{
    
    
	int n, m; cin >> n >> m;
	rep(i, n) {
    
    
		int x; scanf("%d", &x);
		root[i] = build(x, i, L, R, root[i - 1]);
	}

	rep(i, m) {
    
    
		int l, r; scanf("%d %d", &l, &r);
		printf("%d\n", ask(L, R, l, root[r]));
	}

	return 0;
}

莫队

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, B = 450;
int w[N];
struct mo {
    
    
	int l, r, id;
	bool operator< (const mo& t) const {
    
    
		if (l / B != t.l / B) return l < t.l;
		return l / B & 1 ? r < t.r : r > t.r;
	}
}; vector<mo> area;

int res[N];
int cou[N], bcou[B];
void add(int c) {
    
     if (++cou[c] == 1) bcou[c / B]++; }
void sub(int c) {
    
     if (--cou[c] == 0) bcou[c / B]--; }
int ask() {
    
    
	for (int i = 0; i < B; ++i) {
    
    
		if (bcou[i] == B) continue;
		int l = B * i, r = l + B - 1;
		for (int j = l; j <= r; ++j) {
    
    
			if (!cou[j]) return j;
		}
	}
	assert(0);
}
int main()
{
    
    
	int n, m; cin >> n >> m;
	rep(i, n) scanf("%d", &w[i]);

	rep(i, m) {
    
    
		int l, r; scanf("%d %d", &l, &r);
		area.push_back({
    
     l, r, i });
	}
	sort(area.begin(), area.end());

	int L = 1, R = 0;
	for (auto& [l, r, id] : area) {
    
    
		while (l < L) add(w[--L]);
		while (r > R) add(w[++R]);
		while (L < l) sub(w[L++]);
		while (R > r) sub(w[R--]);

		res[id] = ask();
	}

	rep(i, m) printf("%d\n", res[i]);

    return 0;
}

END

猜你喜欢

转载自blog.csdn.net/weixin_45799835/article/details/121274074