「学习笔记/刷题记录」带修莫队算法([国家集训队] 数颜色 / 维护队列)
前言
玩了半个下午的卡常,快玩出花了,我都怀疑最后可能有人会把我举报卡评测,如下图
只能说,inline
是个好东西,直接快了近 \(2s\)
进入正题
要知道,最普通的莫队算法是不支持修改的,前面也提到过,这个带修莫队(可持久化莫队)是后来人改进得来的(好像还改进到在线莫队了?我不清楚,错了别喷),在存储方面,带修莫队只是增加了新的一维——时间轴,代表这是第几次修改后的查询
来看看下面这道题(第一次交全RE了)
[国家集训队] 数颜色 / 维护队列
墨墨购买了一套 \(N\) 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:
1、\(Q\ L\ R\) 代表询问你从第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔。
2、\(R\ P\ Col\) 把第 \(P\) 支画笔替换为颜色 \(Col\)。
为了满足墨墨的要求,你知道你需要干什么了吗?
和【小Z的袜子】相比,这道题不用维护概率那个恶心人的东西,只需要维护数量就好了,它的 add() 和 del() 函数和一般的莫队写法都差不多,只是注意,在本题中如果某种颜色的画笔数量减为 \(0\) 了,那么 \(ans\) 就要 \(-1\);某种画笔的数量从 \(0\) 变成了 \(1\),那么 \(ans\) 就要 \(+1\)
而我们多了时间轴这意味,可以这么理解一下,如果当前正在查询的时间轴 \(T\),比你现在的时间轴 \(t\) 要大,那你就把你的 \(t\) 改到 \(T\),就是你改少了再多改写,同理,你改多了就再改回来
具体怎么改,看代码吧,其实只要搞懂莫队的思想,带修莫队也没什么能讲的
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
const int N = 2e6 + 5;
int n, m, num, cnta, cntc, ans;
int c[N], pos[N], cnt[N], res[N];
struct xunwen {
int l, r, t, id;
int operator < (const xunwen &b) {
return pos[l] == pos[b.l] ? pos[r] == pos[b.r] ? t < b.t :
pos[r] < pos[b.r] : pos[l] < pos[b.l];
}
} ask[N];
struct xiugai {
int l, r;
} change[N];
inline void add(int x) {
ans += !cnt[x];
++ cnt[x];
}
inline void del(int x) {
ans -= !-- cnt[x];
}
inline void pushup(int x, int t) {
if (ask[x].l <= change[t].l && change[t].l <= ask[x].r) {
del(c[change[t].l]);
add(change[t].r);
}
swap(change[t].r, c[change[t].l]);
}
int main() {
n = read(), m = read();
num = pow(n, 0.666);
for (int i = 1; i <= n; ++ i) {
c[i] = read();
pos[i] = (i - 1) / num + 1;
}
for (int i = 1; i <= m; ++ i) {
char op[5];
scanf("%s", op);
int l = read(), r = read();
if (op[0] == 'Q') {
ask[++ cnta].l = l;
ask[cnta].r = r;
ask[cnta].id = cnta;
ask[cnta].t = cntc;
}
else {
change[++ cntc].l = l;
change[cntc].r = r;
}
}
sort(ask + 1, ask + cnta + 1);
int l = 0, r = -1, t = 0;
for (int i = 1; i <= cnta; ++ i) {
while (l < ask[i].l) {
del(c[l ++]);
}
while (l > ask[i].l) {
add(c[-- l]);
}
while (r < ask[i].r) {
add(c[++ r]);
}
while (r > ask[i].r) {
del(c[r --]);
}
while (t < ask[i].t) {
pushup(i, ++ t);
}
while (t > ask[i].t) {
pushup(i, t --);
}
res[ask[i].id] = ans;
}
for (int i = 1; i <= cnta; ++ i) {
printf("%d\n", res[i]);
}
return 0;
}
朝气蓬勃 后生可畏