上一篇讲了基础的莫队算法,个人觉得只要理解入队和出队的操作以及如何对出入进行数据处理就可以快速上手莫队。今天带来的是莫队算法的小拓展,带修莫队。
带修莫队,顾名思义就是在莫队跑区间的过程中对数组的部分内容进行修改,变化,然后继续跑基础莫队。话不多说,贴一道经典带修莫队的模板题:https://www.luogu.com.cn/problem/P1903https://www.luogu.com.cn/problem/P1903
题目描述
墨墨购买了一套 𝑁 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:
-
𝑄 𝐿 𝑅 代表询问你从第 𝐿 支画笔到第 𝑅 支画笔中共有几种不同颜色的画笔。
-
𝑅 𝑃 𝐶 把第 𝑃P 支画笔替换为颜色 𝐶。
为了满足墨墨的要求,你知道你需要干什么了吗?
输入格式
第 1 行两个整数 𝑁,𝑀,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第 2 行 𝑁 个整数,分别代表初始画笔排中第 𝑖 支画笔的颜色。
第 3 行到第 2+𝑀 行,每行分别代表墨墨会做的一件事情,格式见题干部分。
输出格式
对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 𝐿支画笔到第 𝑅 支画笔中共有几种不同颜色的画笔。
输入输出样例
输入
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
说明/提示
对于30%的数据,𝑛,𝑚≤10000n,m≤10000
对于60%的数据,𝑛,𝑚≤50000n,m≤50000
对于所有数据,𝑛,𝑚≤133333n,m≤133333
所有的输入数据中出现的所有整数均大于等于 11 且不超过 106106。
本题可能轻微卡常数
先读题,我们可以看到出现一种全新命令,R命令,作用就是修改颜色,而我们知道,进行基础莫队算法时,我们需要将查询区间进行一个二关键词排序,但是如果这道题我们也先排序,就会打乱查询和修改的顺序,导致无法得知什么时候要修改,以及修改后会对答案产生什么贡献。为了解决这个问题,我们在区间结构体中加入一个新的关键词time,即时间戳的概念,表示了当前查询之前需要几次修改,那上述例题来看,询问1,2的时间戳为0,代表不需要修改。而4,5行输入的查询区间的时间戳就是1,表示在此查询之前有1次修改,即第三行的修改内容。
经过这样的更新,不妨将时间戳也纳入关键词排序的过程中,升级成三关键词排序。
更新后代码如下:
struct node{
int l;
int r;
int time;
int idx;
}Q[N];
struct Node{
int div;
int key;
}R[N];
int T, n, m, siz, res;
int a[N], ans[N], cnt[N];
bool cmp(const node x, const node y)
{
if(x.l / siz != y.l / siz)
return x.l < y.l;
if(x.r / siz != y.r / siz)
return x.r < y.r;
return x.time < y.time;
}
加入时间戳后,在主函数中也要加入相应的部分实现对修改命令的执行与复归,因此,我们先要判断当前时间轴和时间戳的关系,如果当前时间轴在目标时间戳之前,意味着需要进行修改操作,并将时间轴后移,反之,需要进行复原操作,将时间轴前移,这一部分代码如下:
while(now < Q[i].time)
{
int v = R[++ now].div;
if(v >= l && v <= r)
{
Sub(a[v]);
Add(R[now].key);
}
swap(a[v], R[now].key);
}
while(now > Q[i].time)
{
int v = R[now].div;
if(v >= l && v <= r)
{
Sub(a[v]);
Add(R[now].key);
}
swap(a[v], R[now --].key);
}
在进行修改和复原操作时,我们需要先考虑将原始值进行Sub操作,去除其贡献,然后将修改后值的贡献加入res中;
这样就实现了基础莫队算法的修改过程;
附上完整代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7 + 10;
struct node{
int l;
int r;
int time;
int idx;
}Q[N];
struct Node{
int div;
int key;
}R[N];
int T, n, m, siz, res;
int a[N], ans[N], cnt[N];
bool cmp(const node x, const node y)
{
if(x.l / siz != y.l / siz)
return x.l < y.l;
if(x.r / siz != y.r / siz)
return x.r < y.r;
return x.time < y.time;
}
void Add(int n)
{
cnt[n] ++;
if(cnt[n] - 1 == 0)
res ++;
}
void Sub(int n)
{
cnt[n] --;
if(cnt[n] == 0)
res --;
}
int main()
{
cin >> n >> m;
siz = pow(1.0 * n, 2.0 / 3.0);
int tt = 0, ii = 0, x, y;
char cmd[2];
for(int i = 1; i <= n; i ++)
cin >> a[i];
for(int i = 0; i < m; i ++)
{
cin >> cmd >> x >> y;
if(cmd[0] == 'Q')
Q[++ ii] = {x, y, tt, ii};
else
R[++ tt] = {x, y};
}
sort(Q + 1, Q + ii + 1, cmp);
int l = 1, r = 0, now = 0;
for(int i = 1; i <= ii; i ++)
{
while(Q[i].l > l) Sub(a[l ++]);
while(Q[i].l < l) Add(a[-- l]);
while(Q[i].r > r) Add(a[++ r]);
while(Q[i].r < r) Sub(a[r --]);
while(now < Q[i].time)
{
int v = R[++ now].div;
if(v >= l && v <= r)
{
Sub(a[v]);
Add(R[now].key);
}
swap(a[v], R[now].key);
}
while(now > Q[i].time)
{
int v = R[now].div;
if(v >= l && v <= r)
{
Sub(a[v]);
Add(R[now].key);
}
swap(a[v], R[now --].key);
}
ans[Q[i].idx] = res;
}
for(int i = 1; i <= ii; i ++)
cout << ans[i] << endl;
}