P1903 [国家集训队] 数颜色 / 维护队列的解法和疑惑(未解答)

本文介绍了一个涉及IT技术的算法问题,描述了如何高效地处理墨墨购买的彩色画笔中颜色查询和修改的场景,要求在队列操作中快速计算不同颜色的数量。
摘要由CSDN通过智能技术生成

# [国家集训队] 数颜色 / 维护队列

## 题目描述

墨墨购买了一套 N 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:

1. Q  L  R 代表询问你从第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。

2. R P C 把第 P 支画笔替换为颜色 C。

为了满足墨墨的要求,你知道你需要干什么了吗?

## 输入格式

第 1 行两个整数 N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。

第 2 行 N 个整数,分别代表初始画笔排中第 $i$ 支画笔的颜色。

第 3 行到第 2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。

## 输出格式

对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 $L$ 支画笔到第 $R$ 支画笔中共有几种不同颜色的画笔。

## 样例 #1

### 样例输入 #1


6 5
1 2 3 4 5 5
Q 1 4
Q 2 6

R 1 2
Q 1 4
Q 2 6
```

### 样例输出 #1

```
4
4
3
4
```

本题是一道待修改莫队的模板题。 非常折磨人尤其是像我这样刚学莫队的,而且我还遇到了一个究极玄学的问题。

代码如下:

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAX_NM = 133333 + 5;
const int MAX_COLOR = 1000000 + 5;
int n, m, siz, bnum, nowans, cntq, cntm;
int c[MAX_NM], cnt[MAX_COLOR], belong[MAX_NM], ans[MAX_NM];// !!!!! 该地方的后面两个数组如
// 果换位置的话题目的后三个测试点会出错,这里的原因是为什么我也不清楚,挺玄学的(如果有大佬知道,
// 非常欢迎评论区解惑)
struct query {
	int l, r, tim, id; // f 作为修改位当f = 1时对c里面的颜色进行修改(默认为0)
} q[MAX_NM];
struct modify {
	int pos, color, last;
} mod[MAX_NM];

int read() {
	int x = 0;
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		x = (x << 3) + (x << 1) + (c ^ '0');
		c = getchar();
	}
	return x;
}
void write(int x) {
	if (x / 10) write(x / 10);
	// 123    123%10 = 3  12 % 10 = 2;
	putchar(x % 10 + '0');
	return;
}
/*
  int cmp(query a, query b) {
  return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] :
  ((belong[a.r] ^ belong[b.r]) ? belong[a.r] < belong[b.r] : a.time < b.time);
  }
 */
// 第一关键字 左端点所属块   第二关键字 右端点所属块    第三关键字 询问的时间戳(time)
int cmp(query a, query b) {
	return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : 
	((belong[a.r] ^ belong[b.r]) ? belong[a.r] < belong[b.r] : a.tim < b.tim);
}

int main() {
	n = read();
	m = read();
	char opt;
	siz = pow(n, 2.0 / 3);
	bnum = ceil((double)n / siz);
	for (int i = 1; i <= bnum; i++) {
		for (int j = (i - 1) * siz + 1; j <= i * siz; j++) {
			belong[j] = i;
		}
	}
	for (int i = 1; i <= n; i++) c[i] = read();
	for (int i = 1; i <= m; i++) {
		cin >> opt; 
    // 这个位置opt除了cin之外 还可以用scanf输入一个opt数组(%s)  后面的if语句则可以
    // if(opt[0] == 'Q')  但是不能scanf一个字符(%c)
    // 此处原因我也不懂,想去也没头绪,不知道找哪个方向。 有大佬路过欢迎指点(指点指点吧,帮帮孩子)
		if (opt == 'Q') {
			q[++cntq].l = read();
			q[cntq].r = read();
			q[cntq].tim = cntm;
			q[cntq].id = cntq;
		} else if (opt == 'R') {
			mod[++cntm].pos = read();
			mod[cntm].color = read();
		}
	}
	sort(q + 1, q + 1 + cntq, cmp);
	int l = 1, r = 0, t = 0;
	for (int i = 1; i <= cntq; i++) {
		int ql = q[i].l, qr = q[i].r, qt = q[i].tim;
		while (l < ql) nowans -= !--cnt[c[l++]];
		while (l > ql) nowans += !cnt[c[--l]]++;
		while (r < qr) nowans += !cnt[c[++r]]++;
		while (r > qr) nowans -= !--cnt[c[r--]];
		/*
		  从1位置的时间戳  到  当前询问所对应的时间戳
		  每次将nowans的值进行改变   将原位置的颜色在 cnt 数组中的值减一, 减完后值为0-->nowans--
			                         将修改后的颜色在 cnt 数组中的值加一, 先判断值是否为0-->nowans++
		 */
		while (t < qt) {
			++t;
			//     区间内待修改位置原来的颜色对应的出现次数减少一次后判断其是否为0  为0则得1 否则得0
			//	   修改后的颜色所对应的出现次数先判断其是否为0 为0得1 否则得0   之后进行出现次数加1操作
			if (ql <= mod[t].pos && mod[t].pos <= qr) nowans -= !--cnt[c[mod[t].pos]] - !cnt[mod[t].color]++;
			//                           化简操作(易理解): nowans -= !--cnt[c[mod[time].pos]];
			//											   nowans += !cnt[mod[time].color]++;
			swap(c[mod[t].pos], mod[t].color);
		}
		while (t > qt) {
			//     该循环所要进行的操作是将修改后的颜色进行复原
			//     由于前面的swapp 将mod数组中 修改后的颜色与修改前的颜色进行了交换, 所以操作可变化为
			//     将修改后的颜色视为未修改的颜色, 然后对其进行修改 即可将颜色进行复原
			//     区间内待修改位置原来的颜色对应的出现次数减少一次后判断其是否为0  为0则得1 否则得0
			//     修改后的颜色所对应的出现次数先判断其是否为0 为0得1 否则得0   之后进行出现次数加1操作
			if (ql <= mod[t].pos && mod[t].pos <= qr) nowans -= !--cnt[c[mod[t].pos]] - !cnt[mod[t].color]++;
			//                           化简操作(易理解): nowans -= !--cnt[c[mod[time].pos]];
			//											   nowans += !cnt[mod[time].color]++;
			swap(c[mod[t].pos], mod[t].color);
			t--;
		}
		ans[q[i].id] = nowans;
	}

	for (int i = 1; i <= cntq; i++) {
		write(ans[i]);
		putchar('\n');
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值