基础莫队
题目:
HH 有一串由各种漂亮的贝壳组成的项链。
HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。
HH 不断地收集新的贝壳,因此他的项链变得越来越长。
有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?
这个问题很难回答,因为项链实在是太长了。
于是,他只好求助睿智的你,来解决这个问题。
输入格式
第一行:一个整数 NN,表示项链的长度。
第二行:NN 个整数,表示依次表示项链中贝壳的编号(编号为 00 到 10000001000000 之间的整数)。
第三行:一个整数 MM,表示 HH 询问的个数。
接下来 MM 行:每行两个整数,LL 和 RR,表示询问的区间。
输出格式
MM 行,每行一个整数,依次表示询问对应的答案。
数据范围
1≤N≤500001≤N≤50000,
1≤M≤2×1051≤M≤2×105,
1≤L≤R≤N1≤L≤R≤N输入样例:
6 1 2 3 4 3 5 3 1 2 3 5 2 6输出样例:
2 2 4
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 50010, M = 200010, S = 1000010;
int n, m, T;
int w[N], ans[M];
struct Q{
int l, r, i, x, y;
inline Q() {}
inline Q(int l, int r, int i) : l(l), r(r), i(i) {x = l / T, y = r;}
inline friend bool operator< (const Q &a, const Q &b)
{
return a.x == b.x ? (a.x & 1) ? a.y < b.y : a.y > b.y : a.x < b.x;
}
}q[M];
int cnt[S];
void add(int x, int& res)
{
//第一次出现,说明出现了新的贝壳,标记+1
if (!cnt[x]) res ++;
cnt[x] ++;
}
void del(int x, int& res)
{
//将当前-1,如果-1为0,则贝壳种类-1
cnt[x] --;
if (!cnt[x]) res --;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
scanf("%d", &m);
T = sqrt((double) n * n / m);
for (int i = 0; i < m; i ++ )
{
int l, r; scanf("%d%d", &l, &r);
q[i] = {l, r, i};
}
sort(q, q + m);
// for (int i = 0; i < m; i ++ )
// printf("[l = %d, r = %d]
", q[i].l, q[i].r);
for (int k = 0, l = 1, r = 0, res = 0; k < m; k ++ )
{
while (l < q[k].l) del(w[l ++ ], res);
while (l > q[k].l) add(w[-- l ], res);
while (r < q[k].r) add(w[++ r ], res);
while (r > q[k].r) del(w[r -- ], res);
ans[q[k].i] = res;
}
for (int i = 0; i < m; i ++ ) printf("%d
", ans[i]);
return 0;
}
待修莫队
题目:
墨墨购买了一套 NN 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。
墨墨会像你发布如下指令:
Q L R代表询问你从第 LL 支画笔到第 RR 支画笔中共有几种不同颜色的画笔。R P Col把第 PP 支画笔替换为颜色 ColCol。为了满足墨墨的要求,你知道你需要干什么了吗?
输入格式
第 11 行两个整数 N,MN,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第 22 行 NN 个整数,分别代表初始画笔排中第 ii 支画笔的颜色。
第 33 行到第 2+M2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。
输出格式
对于每一个
Query的询问,你需要在对应的行中给出一个数字,代表第 LL 支画笔到第 RR 支画笔中共有几种不同颜色的画笔。数据范围
1≤N,M≤100001≤N,M≤10000,
修改操作不多于 10001000 次,
所有的输入数据中出现的所有整数均大于等于 11 且不超过 106106。输入样例:
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
分析:
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, S = 1000010;
int n, m, mq, mc, len;
int w[N], cnt[S], ans[N];
struct Query{
int id, l, r, t;
}q[N];
struct Modify{
int 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[3];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (*op == 'Q')
{
//多加一维,t,表示在查询区间[L,R]的时候,已经修改了t次,可以将t理解为时间范围[1,m]
q[++ mq] = {mq, a, b, mc};
}
else
{
c[++ mc] = {a, b};
}
}
len = cbrt((double)n * mc) + 1;
sort(q + 1, q + 1 + mq, cmp);
for (int i = 1, l = 1, r = 0, t = 0, res = 0; i <= mq; i ++ )
{
while (l < q[i].l) del(w[l ++ ], res);
while (l > q[i].l) add(w[-- l ], res);
while (r < q[i].r) add(w[++ r ], res);
while (r > q[i].r) del(w[r -- ], res);
while (t < q[i].t)
{
t ++;
if (c[t].p >= l && c[t].p <= r)
{
del(w[c[t].p], res);
add(c[t].c, res);
}
swap(w[c[t].p], c[t].c);
}
while (t > q[i].t)
{
if (c[t].p >= l && c[t].p <= r)
{
del(w[c[t].p], res);
add(c[t].c, res);
}
swap(w[c[t].p], c[t].c);
t --;
}
ans[q[i].id] = res;
}
for (int i = 1; i <= mq; i ++ ) printf("%d
", ans[i]);
return 0;
}
回滚莫队
题目:
IOI 国历史研究的第一人——JOI 教授,最近获得了一份被认为是古代 IOI 国的住民写下的日记。
JOI 教授为了通过这份日记来研究古代 IOI 国的生活,开始着手调查日记中记载的事件。
日记中记录了连续 NN 天发生的时间,大约每天发生一件。
事件有种类之分。第 ii 天 (1≤i≤N)(1≤i≤N) 发生的事件的种类用一个整数 XiXi 表示,XiXi 越大,事件的规模就越大。
JOI 教授决定用如下的方法分析这些日记:
- 选择日记中连续的一些天作为分析的时间段
- 事件种类 tt 的重要度为 t×t× (这段时间内重要度为 tt 的事件数)
- 计算出所有事件种类的重要度,输出其中的最大值
现在你被要求制作一个帮助教授分析的程序,每次给出分析的区间,你需要输出重要度的最大值。
输入格式
第一行两个空格分隔的整数 NN 和 QQ,表示日记一共记录了 NN 天,询问有 QQ 次。
接下来一行 NN 个空格分隔的整数 X1…XNX1…XN,XiXi 表示第 ii 天发生的事件的种类。
接下来 QQ 行,第 ii 行 (1≤i≤Q)(1≤i≤Q) 有两个空格分隔整数 AiAi 和 BiBi,表示第 ii 次询问的区间为 [Ai,Bi][Ai,Bi]。
输出格式
输出 QQ 行,第 ii 行 (1≤i≤Q)(1≤i≤Q) 一个整数,表示第 ii 次询问的最大重要度。
数据范围
1≤N≤1051≤N≤105,
1≤Q≤1051≤Q≤105,
1≤Xi≤1091≤Xi≤109输入样例:
5 5 9 8 7 8 9 1 2 3 4 4 4 1 4 2 4输出样例:
9 8 8 16 16
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m, T;
int a[N], cnt[N];
LL ans[N];
struct Query{
int id, l, r;
}q[N];
vector<int> nums;
int get(int x)
{
return x / T;
}
bool cmp(const Query& a, const Query& b)
{
int x = get(a.l), y = get(b.l);
if (x != y) return x < y;
return a.r < b.r;
}
void add(int x, LL& ans)
{
if (!cnt[x]) ans ++;
cnt[x] ++;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &a[i]);
nums.push_back(a[i]);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
for (int i = 1; i <= n; i ++ )
a[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();
for (int i = 0; i < m; i ++ )
{
int l, r; scanf("%d%d", &l, &r);
q[i] = {i, l, r};
}
sort(q, q + m);
for (int x; x < m;)
{
int y = x;
//将左区间在同一块内的所有区间找到
while (y < m && get(q[x].l) == get(q[y].l)) y ++;
//找到当前块的区间右端点
int right = get(q[x].l) * T + T - 1;
//将所有在同一块内的所有询问暴力求解
while (x < y && q[x].r <= right)
{
LL res = 0;
for (int i = q[x].l; i <= q[x].r; i ++ ) add(a[i], res);
ans[q[x].id] = res;
//这里的每个询问的区间左端点是按照分块排序的,所以,在分块内,不具有递增性
//所以,我们不可能只进行add操作就可以将区间求解出来,而是需要不断地反复求取
//所以,在求解的时候,需要add,再清空,再add
for (int i = q[x].l; i <= q[x].r; i ++ ) cnt[a[i]] --;
x ++;
}
//求块间的询问,询问的区间左端点在同一块中,右端点在其他块中,按递增顺序
LL res = 0;
int l = right + 1, r = right;
while (x < y)
{
//将r扩展到当前询问的区间右端点,在后面的块内
while (r < q[x].r) add(a[++ r], res);
//res在后面的分块内是不变的,但是会被l所在分块修改,所以,先备份后面分块的res
LL backup = res;
//将l扩展到当前询问的区间左端点,在l所在的块内
while (l > q[x].l) add(a[-- l], res);
ans[q[x].id] = res;
//将l所在块内造成的影响清除掉[保证了只会删到第一个区间的right]
while (l < right + 1) cnt[a[x ++ ]] --;
//恢复后面分块的res,消除l所在分块的影响
res = backup;
x ++;
}
//每移动到新的分块后,我们的区间左端点的add要重算,不如直接清空了
memset(cnt, 0, sizeof cnt);
}
for (int i = 0; i < m; i ++ ) printf("%lld
", ans[i]);
return 0;
}
树上莫队
题目:
给定一棵 NN 个节点的树,节点编号从 11 到 NN,每个节点都有一个整数权值。
现在,我们要进行 MM 次询问,格式为
u v,对于每个询问你需要回答从 uu 到 vv 的路径上(包括两端点)共有多少种不同的点权值。输入格式
第一行包含两个整数 N,MN,M。
第二行包含 NN 个整数,其中第 ii 个整数表示点 ii 的权值。
接下来 N−1N−1 行,每行包含两个整数 x,yx,y,表示点 xx 和点 yy 之间存在一条边。
最后 MM 行,每行包含两个整数 u,vu,v,表示一个询问。
输出格式
共 MM 行,每行输出一个询问的答案。
数据范围
1≤N≤400001≤N≤40000,
1≤M≤1051≤M≤105,
1≤x,y,u,v≤N1≤x,y,u,v≤N,
各点权值均在 intint 范围内。输入样例:
8 2 105 2 9 3 8 5 7 7 1 2 1 3 1 4 3 5 3 6 3 7 4 8 2 5 3 8输出样例:
4 4
分析:
树上的莫队,通过DFS序,或者说欧拉序列将树上的节点,映射成下标
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, len;
int h[N], e[N], ne[N], idx;
int w[N];
vector<int> nums;
int fa[N][17], depth[N];
int seq[N], top, first[N], last[N];
int cnt[N], st[N], ans[N];
struct Query{
int id, l, r, p;
}q[N];
void add_edges(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int x, int father)
{
seq[++ top] = x;
first[x] = top;
for (int i = h[x]; ~i; i = ne[i])
{
int y = e[i];
if (y == father) continue;
dfs(y, x);
}
seq[ ++ top] = x;
last[x] = top;
}
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
queue<int> q;
q.push(1);
while (!q.empty())
{
int x = q.front();
q.pop();
for (int i = h[x]; ~i; i = ne[i])
{
int y = e[i];
if (depth[y] > depth[x] + 1)
{
depth[y] = depth[x] + 1;
q.push(y);
fa[y][0] = x;
for (int k = 1; k <= 16; k ++ )
fa[y][k] = fa[fa[y][k - 1]][k - 1];
}
}
}
}
int LCA(int x, int y)
{
if (depth[x] < depth[y]) swap(x, y);
for (int k = 16; k >= 0; k -- )
if (depth[fa[x][k]] >= depth[y])
x = fa[x][k];
if (x == y) return y;
for (int k = 15; k >= 0; k -- )
if (fa[x][k] != fa[y][k])
{
x = fa[x][k];
y = fa[y][k];
}
return fa[x][0];
}
int get(int x)
{
return x / len;
}
bool cmp(const Query& a, Query& b)
{
int x = get(a.l), y = get(b.l);
if (x != y) return x < y;
return a.r < b.r;
}
void add(int x, int& res)
{
//注意这里的x是传入的树的节点,也就是我们映射后的数组下标,并不是元素值
st[x] ^= 1;
if (st[x] == 0)
{
cnt[w[x]] --;
if (!cnt[w[x]]) res --;
}
else
{
if (!cnt[w[x]]) res ++;
cnt[w[x]] ++;
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &w[i]);
nums.push_back(w[i]);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
for (int i = 1; i <= n; i ++ )
w[i] = lower_bound(nums.begin(), nums.end(), w[i]) - nums.begin();
for (int i = 0; i < n - 1; i ++ )
{
int x, y; scanf("%d%d", &x, &y);
add_edges(x, y); add_edges(y, x);
}
dfs(1, -1);
bfs();
for (int i = 0; i < m; i ++ )
{
int x, y; scanf("%d%d", &x, &y);
if (first[x] > first[y]) swap(x, y);
int father = LCA(x, y);
if (father == x) q[i] = {i, first[x], first[y]};
else q[i] = {i, last[x], first[y], father};
}
len = sqrt(top);
sort(q, q + m, cmp);
for (int i = 0, l = 1, r = 0, res = 0; i < m; i ++ )
{
while (l < q[i].l) add(seq[l ++ ], res);
while (l > q[i].l) add(seq[-- l ], res);
while (r < q[i].r) add(seq[++ r ], res);
while (r > q[i].r) add(seq[r -- ], res);
if (q[i].p) add(q[i].p, res);
ans[q[i].id] = res;
if (q[i].p) add(q[i].p, res);
}
for (int i = 0; i < m; i ++ ) printf("%d
", ans[i]);
return 0;
}