天天看點

【ybt金牌導航6-3-4】【luogu P1903】數顔色 / 維護隊列(分塊)(二分)數顔色 / 維護隊列

數顔色 / 維護隊列

題目連結:ybt金牌導航6-3-4 / luogu P1903

題目大意

給你一個序列,要你支援一些操作:

把一個數換成另一個數,查詢一個區間中有多少個不同的數。

思路

我們考慮用分塊來做。

那我們要先知道如何判斷這個數在某一塊中是否是第一次出現。

我們可以搞一個 b e f i bef_i befi​ 記錄跟這個數一樣的它前面的最後一個數是哪裡。

那如果 b e f i < l bef_i<l befi​<l,那它就是在 l l l 後面的這個塊中第一次出現。

那不難想到要怎麼暴力搞,那我們接着看整塊怎麼搞。

不難想到排序之後二分。

那接着看修改。發現修改之後有三個東西會影響:

  1. 後面第一個跟它原來顔色的數( b e f bef bef 值是 x x x 的數)
  2. 後面第一個跟它新的顔色的數( b e f bef bef 将要是 x x x 的數)
  3. 前面第一個跟它新的顔色的數( x x x 的 b e f bef bef 将要是它)

那修改它們的 b e f bef bef 不難想到怎麼搞,但問題是如何找到。

如果在塊内,我們可以直接暴力找。跑出塊之後,我們就一個一個塊的找,不難想到可以通過二分判斷一個數是否在一個塊中。(把塊中數排序)

然後搞搞就行了,代碼比較多,煩,可以看看代碼具體實作。

然後由于 luogu 需要卡常,我快讀加了之後,還加了一個優化:

當修改 b e f bef 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;
}
           

繼續閱讀