数颜色 带修改莫队

原题

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

墨墨会像你发布如下指令:

Q L R 代表询问你从第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。
R P Col 把第 P 支画笔替换为颜色 Col。
为了满足墨墨的要求,你知道你需要干什么了吗?

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

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

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

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

数据范围
1≤N,M≤10000,
修改操作不多于 1000 次,
所有的输入数据中出现的所有整数均大于等于 1 且不超过 106。

输入样例:
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
输出样例:
4
4
3
4

感觉这是到现在为止我遇到的最奇妙的算法了。
普通莫队大多是以改变问询顺序来实现时间复杂度的优化。但这种莫队无法应付需要修改的情况。
如果说普通莫队是在一维线上暴力,那么带修改莫队就是在二维平面上暴力。多出来的一维就是时间轴。
每次修改是一次时间的改变。例如针对N长数组有K次修改,设一个数组
f[N][K]f[i][j]表示第j次修改时,第i个数的值,在执行询问时直接跳到该询问对应的j值即可。
当然,我们不需要真的开一个二维数组,因为空间不够。所以给每个问询赋一个时间戳,在处理这个问询时,如果当前时间小于问询时间戳,那么就遍历这两个时间之间的所有修改。如果当前时间大于询问时间戳,就进行还原,具体还原的方法在下面的代码里。

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstring>
using namespace std;

const int N = 10010, S = 1000010;

int n, m, len;
int mq, mc; //询问操作和修改操作的数量
int w[N], cnt[S], ans[N];
struct Query { //所有的查询操作
	int id, l, r, t; //时间戳
} q[N];

struct Modify { //所有的修改操作
	int p, c; //将第p个数修改成c
} c[N];

int get(int x) {
	return x / len;
}

bool cmp(const Query &a, const Query &b) {
	int al = get(a.l), ar = get(a.r);
	int bl = get(b.l), br = get(b.r);
	if (al != bl)
		return al < bl;
	if (ar != br)
		return ar < br;
	return a.t < b.t;
}

void add(int x, int &res) {
	if (!cnt[x])
		res ++ ;
	cnt[x] ++ ;
}

void del(int x, int &res) {
	cnt[x] -- ;
	if (!cnt[x])
		res -- ;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i ++ )
		scanf("%d", &w[i]);
	for (int i = 0; i < m; i ++ ) {
		char op[2];
		int a, b;
		scanf("%s%d%d", op, &a, &b);
		if (*op == 'Q')
			mq ++, q[mq] = {mq, a, b, mc};
		else
			c[ ++ mc] = {a, b};
	}

	len = cbrt((double)n * mc) + 1;
	sort(q + 1, q + mq + 1, cmp);

	for (int i = 0, j = 1, t = 0, k = 1, res = 0; k <= mq; k ++ ) {
		int id = q[k].id, l = q[k].l, r = q[k].r, tm = q[k].t;
		while (i < r)
			add(w[ ++ i], res);
		while (i > r)
			del(w[i -- ], res);
		while (j < l)
			del(w[j ++ ], res);
		while (j > l)
			add(w[ -- j], res);
		//在基础的线性修改的情况下再加入一个时间轴修改
		while (t < tm) {//如果当前时间小于该询问的时间,就要将这个期间的修改加上
			t ++ ;//当前时间的修改也要加上
			//p:第p个数 c修改成为的值
			if (c[t].p >= j && c[t].p <= i) {
				del(w[c[t].p], res);
				add(c[t].c, res);
			}
			swap(w[c[t].p], c[t].c);//将w中的数也完成修改。
									//当时间逆流时能实现数据恢复,c[t].c储存着当时的数据
		}
		while (t > tm) {//当当前时间大于询问时间,逆流,沿途进行数据恢复
			if (c[t].p >= j && c[t].p <= i) {
				del(w[c[t].p], res);
				add(c[t].c, res);
			}
			swap(w[c[t].p], c[t].c);
			t -- ;
		}
		ans[id] = res;
	}

	for (int i = 1; i <= mq; i ++ )
		printf("%d\n", ans[i]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值