zoukankan      html  css  js  c++  java
  • 【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
    ", 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;
    }
    
  • 相关阅读:
    3.1《想成为黑客,不知道这些命令行可不行》(Learn Enough Command Line to Be Dangerous)——下载文件
    rem实现手机页面自动缩放
    Git 常用命令
    使用 canvas+JS绘制钟表
    JS 操作数组的方法
    Node.js Request方法
    兼容浏览器的点击事件
    ES6知识点
    上传项目到github上
    JavaScript 编码风格
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_6-3-4.html
Copyright © 2011-2022 走看看