【ybt金牌导航6-3-4】【luogu P1903】数颜色 / 维护队列(分块)(二分)

数颜色 / 维护队列

题目链接:ybt金牌导航6-3-4 / luogu P1903

题目大意

给你一个序列,要你支持一些操作:
把一个数换成另一个数,查询一个区间中有多少个不同的数。

思路

我们考虑用分块来做。
那我们要先知道如何判断这个数在某一块中是否是第一次出现。
我们可以搞一个 \(bef_i\) 记录跟这个数一样的它前面的最后一个数是哪里。
那如果 \(bef_i<l\),那它就是在 \(l\) 后面的这个块中第一次出现。

那不难想到要怎么暴力搞,那我们接着看整块怎么搞。
不难想到排序之后二分。
那接着看修改。发现修改之后有三个东西会影响:

  1. 后面第一个跟它原来颜色的数(\(bef\) 值是 \(x\) 的数)
  2. 后面第一个跟它新的颜色的数(\(bef\) 将要是 \(x\) 的数)
  3. 前面第一个跟它新的颜色的数(\(x\)\(bef\) 将要是它)

那修改它们的 \(bef\) 不难想到怎么搞,但问题是如何找到。
如果在块内,我们可以直接暴力找。跑出块之后,我们就一个一个块的找,不难想到可以通过二分判断一个数是否在一个块中。(把块中数排序)

然后搞搞就行了,代码比较多,烦,可以看看代码具体实现。

然后由于 luogu 需要卡常,我快读加了之后,还加了一个优化:
当修改 \(bef\) 的时候我们不要直接就把那块重新排序,而是打个标记。
然后到时我们二分之前,如果有标记,就再排序,然后清空标记。
(这样可以几次修改才排一次序,让排序次数最小)

代码

#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

int n, m, a[200001], S, s;
int bl[10001], br[10001], x, y;
int lst[1000001], bef[200001];
int aa[200001], beff[200001];
bool nc[200001];//小小的优化,修改之后先不直接更新排序,要用的时候要修改才修改
char op;

int read() {
	int re = 0, zf = 1;
	char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') zf = -zf;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re * zf;
}

void Sort(int x) {//求排序
	for (int i = bl[x]; i <= br[x]; i++)
		aa[i] = a[i], beff[i] = bef[i];
	sort(aa + bl[x], aa + br[x] + 1);
	sort(beff + bl[x], beff + br[x] + 1);
	nc[x] = 0;
}

void premake() {
	for (int i = 1; i <= n; i++) {
		if (i % S == 1) {
			br[s] = i - 1;
			bl[++s] = i;
		}
	}
	br[s] = n;
	bl[s + 1] = br[s + 1] = n + 1;
	
	for (int i = 1; i <= s; i++) {
		nc[i] = 1;
	}
}

int query(int block, int x) {//找这一块有多少个数的 bef 值小于 x
	if (nc[block]) Sort(block); 
	int l = bl[block], r = br[block], ans = bl[block] - 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (beff[mid] < x) ans = mid, l = mid + 1;
			else r = mid - 1;
	}
	return ans - bl[block] + 1;
}

bool find(int block, int x) {//找这一块中有没有 x 这个数
	if (nc[block]) Sort(block);
	int l = bl[block], r = br[block];
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (aa[mid] == x) return 1;
		if (aa[mid] < x) l = mid + 1;
			else r = mid - 1;
	}
	return 0;
}

int work(int l, int r) {//查询
	int L = (l - 1) / S + 1;
	int R = (r - 1) / S + 1;
	int re = 0;
	if (r - l + 1 <= 2 * S) {
		for (int i = l; i <= r; i++)
			if (bef[i] < l) re++;
		return re;
	}
	if (l == bl[L]) L--; if (r == br[R]) R++;
	for (int i = L + 1; i < R; i++) re += query(i, l);//整块
	for (int i = l; i <= br[L]; i++) if (bef[i] < l) re++;//两边
	for (int i = bl[R]; i <= r; i++) if (bef[i] < l) re++;
	return re;
}

void kill(int x) {//处理后面第一个跟它原来颜色的数(bef 值是 x 的数)
	int in = (x - 1) / S + 1;
	for (int i = x + 1; i <= br[in]; i++)
		if (bef[i] == x) {
			bef[i] = bef[x];
			nc[in] = 1;
			return ;
		}
	for (int i = in + 1; i <= s; i++) {
		if (find(i, a[x])) {
			for (int j = bl[i]; j <= br[i]; j++)
				if (a[j] == a[x]) {
					bef[j] = bef[x];
					nc[i] = 1;
					return ;
				}
		}
	}
}

void change1(int x, int y) {//处理后面第一个跟它新的颜色的数(bef 将要是 x 的数)
	int in = (x - 1) / S + 1;
	for (int i = x + 1; i <= br[in]; i++)
		if (a[i] == a[x]) {
			bef[i] = x;
			nc[in] = 1;
			return ;
		}
	for (int i = in + 1; i <= s; i++) {
		if (find(i, a[x])) {
			for (int j = bl[i]; j <= br[i]; j++)
				if (a[j] == a[x]) {
					bef[j] = x;
					nc[i] = 1;
					return ;
				}
		}
	}
}

void change2(int x, int y) {//找到前面第一个跟它新的颜色的数(x 的 bef 将要是它)
	int in = (x - 1) / S + 1;
	for (int i = x - 1; i >= bl[in]; i--)
		if (a[i] == a[x]) {
			bef[x] = i;
			nc[in] = 1;
			return ;
		}
	for (int i = in - 1; i >= 1; i--)
		if (find(i, a[x])) {
			for (int j = br[i]; j >= bl[i]; j--)
				if (a[j] == a[x]) {
					bef[x] = j;
					nc[in] = 1;
					return ;
				}
		}
	bef[x] = 0;
	nc[in] = 1;
}

void change(int x, int y) {
	change1(x, y);
	change2(x, y);
}

int main() {
	n = read(); m = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read();
		bef[i] = lst[a[i]];
		lst[a[i]] = i;
	}
	
	S = sqrt(n);
	premake();
	
	while (m--) {
		op = getchar();
		while (op != 'Q' && op != 'R') op = getchar();
		if (op == 'Q') {
			x = read(); y = read();
			printf("%d\n", work(x, y));
			continue;
		}
		if (op == 'R') {
			x = read(); y = read();
			if (a[x] == y) continue;
			kill(x);
			a[x] = y;
			change(x, y);
			continue;
		}
	}
	
	return 0;
}
posted @ 2021-07-09 11:00  あおいSakura  阅读(23)  评论(0编辑  收藏  举报