[problem solution] P4117 [Ynoi2018] colorful world

Posted by jefrat72 on Wed, 26 Jan 2022 12:48:40 +0100

meaning of the title

P4117 [Ynoi2018] colorful world

Given a sequence with a length of \ (n \) and \ (m \) operations, each operation can:

  1. Subtract \ (x \) from all values greater than \ (x \) in the interval \ ([l, r] \)

  2. Query the number of occurrences of the value \ (x \) in the interval \ ([l, r] \)

\(1 \leq n \leq 10^6, 1 \leq m \leq 5 \times 10^5, 1 \leq l \leq r \leq n, 0 \leq a_i, x \leq 10^5 + 1\)

thinking

Pre knowledge

The second block.

Block by block + block by block processing + joint set query.

Set the maximum value of \ ([l, r] \) to \ (k \). The first operation can be converted into two forms:

  1. \(2 \times x \leq k \), add \ (x \) to all the values in the value range \ ([0, x] \), and then mark the whole interval with subtraction mark

  2. \(2 \ times x > k \), subtract \ (x \) from all the values in the value field \ ([x + 1, k] \)

You can consider using and query set maintenance here. For each value \ (x \) we maintain a subscript union search set, then we need to maintain:

  1. The value \ (I \) corresponds to the root \ (rt_i \) of the lookup set

  2. And the value corresponding to the root \ (I \) of the query set \ (val_i \)

  3. And look up the size of the set

Then, when modifying, the whole block directly merges the parallel query set corresponding to the two values, and the scattered block directly modifies and reconstructs. When asking, the whole block shall be counted directly and the size of the set shall be checked, and the scattered block shall be counted.

When the block length is \ (\ sqrt{n} \), the space complexity of this method is \ (\ mathcal{O}(n \sqrt{n}) \), which will be \ (MLE \)

Consider block by block processing and count the contribution of each block to \ (m \) queries.

For the whole block \ (x \), when modifying:

  1. If the whole block \ (x \) is completely covered by \ ([l, r] \), directly merge and query the set corresponding to the value

  2. If the whole block \ (x \) is not completely covered by \ ([l, r] \), modify and reconstruct the whole block violently

When querying:

  1. If the whole block \ (x \) is completely covered by \ ([l, r] \), directly count and check the set size

  2. If the whole block \ (x \) is not completely covered by \ ([l, r] \), violence statistics

In this way, only the space required for a single block is used, and the space complexity is \ (\ mathcal{O}(n) \)

Obviously, the value range of the whole block continues to shrink after several subtraction modifications. When the value range size is \ (v \), the enumerated value range is \ (\ mathcal{O}(v) \).

Since we only care about and check the \ (size \) of the set root, we can compress the path. After the path is compressed, the query complexity is \ (\ mathcal{O}(1) \) and the query complexity is \ (\ mathcal{O}(\log n) \)

So the time complexity is \ (\ mathcal{O}(\sqrt{n}m + v \sqrt{n} \log n) \)

The code needs to be slightly optimized. Fast read fast write set inline. If the current whole block has no intersection with the query interval, it will not be queried. If the upper limit of the value range minus the mark is less than \ (x \), it will not be queried.

code

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 1e6 + 5;
const int maxq = 5e5 + 5;
const int maxv = 1e5 + 5;
const int inf = 2147483647;

struct node {
	int opt, l, r, x;
} q[maxq];

int n, m;
int st, ed, cur, lazy;
int a[maxn], val[maxn], fa[maxn], size[maxn];
int rt[maxv];
int ans[maxq];

inline int read() {
	int res = 0;
	char ch = getchar();
	while ((ch < '0') || (ch > '9')) {
		ch = getchar();
	}
	while ((ch >= '0') && (ch <= '9')) {
		res = res * 10 + ch - '0';
		ch = getchar();
	}
	return res;
}

inline void write(int x) {
	if (x > 9) {
		write(x / 10);
	}
	putchar(x % 10 + '0');
}

inline int get(int x) {
	if (fa[x] == x) {
		return x;
	}
	return fa[x] = get(fa[x]);
}

inline void merge(int x, int y) {
	if (rt[y]) {
		fa[rt[x]] = rt[y];
	} else {
		rt[y] = rt[x];
		val[rt[x]] = y;
	}
	size[y] += size[x];
	rt[x] = size[x] = 0;
}

inline void init() {
	cur = -inf, lazy = 0;
	memset(rt, 0, sizeof(rt));
	memset(size, 0, sizeof(size));
}

inline void build(int pos) {
	cur = -inf, lazy = 0;
	for (int i = st; i <= ed; i++) {
		cur = max(cur, a[i]);
		if (!rt[a[i]]) {
			val[i] = a[i];
			fa[i] = i;
			rt[a[i]] = i;
		} else {
			fa[i] = rt[a[i]];
		}
		size[a[i]]++;
	}
}

inline void modify(int l, int r, int x, int pos) {
	for (int i = st; i <= ed; i++) {
		int w = val[get(i)];
		a[i] = w - lazy;
		rt[w] = size[w] = 0;
	}
	for (int i = st; i <= ed; i++) {
		val[i] = 0; 
	}
	l = max(l, st);
	r = min(r, ed);
	for (int i = l; i <= r; i++) {
		if (a[i] > x) {
			a[i] -= x;
		}
	}
	build(pos);
}

inline void update(int x) {
	if ((x << 1) <= (cur - lazy)) {
		for (int i = lazy; i <= x + lazy; i++) {
			if (rt[i]) {
				merge(i, i + x);
			}
		}
		lazy += x;
	} else {
		for (int i = cur; i > lazy + x; i--) {
			if (rt[i]) {
				merge(i, i - x);
			} 
		}
		cur = min(cur, x + lazy);
	}
}

inline int query(int l, int r, int x) {
	int res = 0;
	l = max(l, st);
	r = min(r, ed);
	for (int i = l; i <= r; i++) {
		if (val[get(i)] - lazy == x) {
			res++;
		}
	}
	return res;
}

int main() {
	int opt, l, r, x;
	n = read(), m = read();
	int block = sqrt(n);
	int tot = ceil(n * 1.0 / block);
	for (int i = 1; i <= n; i++) {
		a[i] = read();
	}
	for (int i = 1; i <= m; i++) {
		q[i].opt = read(), q[i].l = read(), q[i].r = read(), q[i].x = read();
	}
	for (int i = 1; i <= tot; i++) {
		init();
		st = (i - 1) * block + 1;
		ed = (i == tot ? n : i * block);
		build(i);
		for (int j = 1; j <= m; j++) {
			if ((q[j].l > ed) || (q[j].r < st)) {
				continue;
			}
			if (q[j].opt == 1) {
				if ((q[j].l <= st) && (ed <= q[j].r)) {
					update(q[j].x);
				} else {
					modify(q[j].l, q[j].r, q[j].x, i);
				}
			} else {
				if (q[j].x + lazy > 1e5 + 1) {
					continue;
				}
				if ((q[j].l <= st) && (ed <= q[j].r)) {
					ans[j] += size[q[j].x + lazy];
				} else {
					ans[j] += query(q[j].l, q[j].r, q[j].x);
				}
			}
		}
	}
	for (int i = 1; i <= m; i++) {
		if (q[i].opt == 2) {
			write(ans[i]);
			putchar('\n');
		}
	}
	return 0;
}