算法笔记莫队算法(基础莫队,带修莫队,回滚莫队,树上莫队,二次离线莫队)
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法笔记莫队算法(基础莫队,带修莫队,回滚莫队,树上莫队,二次离线莫队)相关的知识,希望对你有一定的参考价值。
整理的算法模板合集: ACM模板
目录
来这里学习莫队以及神奇的证明:莫队算法 --算法竞赛专题解析(26)
我们首先考虑双指针的暴力法,发现很容易就会被卡成 O ( n m ) O(nm) O(nm),这时候我们的莫队出现了,莫队说,我可以像变魔术一样,把 O ( n m ) O(nm) O(nm)的算法通过一个神奇的排序方式,使得我们最坏的情况下,时间复杂度也会非常优秀: O ( n n ) O(n\\sqrtn) O(nn)。
莫队算法是一个离线的算法,我们先将所有的询问全部存下来,然后排序。我们的每一个询问都是一个左右区间, ( l , r ) (l ,r) (l,r)
我们的排序方法为双关键字排序,我们将每个询问的左端点
l
l
l 分块。
第一关键字为左端点分块的编号从小到大,第二关键字为右端点的下标从小到大。
编码时,还可以对排序做一个小优化:奇偶性排序,让奇数块和偶数块的排序相反。例如左端点L都在奇数块,则对R从大到小排序;若L在偶数块,则对R从小到大排序(反过来也可以:奇数块从小到大,偶数块从大到小)。
1. 基础莫队
AcWing 2492. HH的项链
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 50007, M = 200007, S = 1000007;
int n, m;
int w[N];
int block;
int cnt[S];
int ans[M];
struct Query
int id, l, r;
q[M];
int get_block(int x)
return x / block;//这里是从0开始
bool cmp(const Query& x, const Query& y)
int a = get_block(x.l);
int b = get_block(y.l);
if(a != b)return a < b;
return x.r < y.r;
void add(int x, int &res)
if(cnt[x] == 0)res ++ ;
cnt[x] ++ ;
void del(int x, int &res)
cnt[x] -- ;
if(cnt[x] == 0)res -- ;
int main()
scanf("%d", &n);
for(int i = 1; i <= n; ++ i) scanf("%d", &w[i]);
scanf("%d", &m);
block = sqrt((double)n * n / m);//1488 ms
//block = sqrt(n); //1700 ms
for(int i = 0; i < m; ++ i)
int l, r;
scanf("%d%d", &l, &r);
q[i] = i, l, r;
sort(q, q + m, cmp);
for(int k = 0, i = 0, j = 1, res = 0; k < m; ++ k)
int id = q[k].id, l = q[k].l, r = q[k].r;
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);//注意这里的细节,自己模拟一遍
ans[id] = res;
for(int i = 0; i < m; ++ i)
printf("%d\\n", ans[i]);
return 0;
玄学优化版,成功卡过了洛谷上的这道题
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1000007, M = 1000007, S = 1000007;
int n, m;
int w[N];
int block;
int cnt[S];
int ans[M];
inline int read()
int x = 0, f = 1;
char ch = getchar();
while(ch > '9' || ch < '0')if(ch == '-')f = -1;ch = getchar();
while(ch >= '0' && ch <= '9')x = x * 10 + ch - '0';ch = getchar();
return x * f;
inline void write(int res)
if(res<0)
putchar('-');
res=-res;
if(res>9)
write(res/10);
putchar(res%10+'0');
struct Query
int id, l, r;
q[M];
inline int get_block(int x)
return x / 2000;//这里是从0开始
bool cmp(const Query& x, const Query& y)
int a = get_block(x.l);
int b = get_block(y.l);
//int a = x.l / block, b = y.l / block;
if(a != b)return a < b;
if(a & 1)return x.r < y.r;
return x.r > y.r;
inline void add(int x, int &res)
if(cnt[x] == 0)res ++ ;
cnt[x] ++ ;
inline void del(int x, int &res)
cnt[x] -- ;
if(cnt[x] == 0)res -- ;
int main()
n = read();
for(register int i = 1; i <= n; ++ i) w[i] = read();
m = read();
block = sqrt((double)n * n / m);//1488 ms
//block = sqrt(n); //1700 ms
//block = 2000;
for(register int i = 0; i < m; ++ i)
int l = read(), r = read();
q[i] = i, l, r;
sort(q, q + m, cmp);
for(register int k = 0, i = 0, j = 1, res = 0; k < m; ++ k)
int id = q[k].id, l = q[k].l, r = q[k].r;
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(i < r)res += ++ cnt[w[ ++ i]] == 1;
while(i > r)res -= -- cnt[w[i -- ]] == 0;
while(j < l)res -= -- cnt[w[j ++ ]] == 0;
while(j > l)res += ++ cnt[w[ -- j]] == 1;
*/
ans[id] = res;
for(register int i = 0; i < m; ++ i)
write(ans[i]), puts("");
return 0;
2. 带修莫队
AcWing 2521. 数颜色
我发现直接把块的大小开成一个常数跑的最快…
//#pragma GCC optimize(2)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1000007, M = 1000007, S = 1000007;
inline int read()
int x = 0, f = 1;
char ch = getchar();
while(ch > '9' || ch < '0')if(ch == '-')f = -1;ch = getchar();
while(ch >= '0' && ch <= '9')x = x * 10 + ch - '0';ch = getchar();
return x * f;
inline void write(int res)
if(res<0)
putchar('-');
res=-res;
if(res>9)
write(res/10);
putchar(res%10+'0');
int n, m;
int block = 2589;//n ^ (2 / 3)
int w[N];
int cnt[S];
int ans[N];
int bi[N];
struct Query
int id, l, r, t;
q[M];
struct Modify
int pos, col, lst;
c[M];
bool cmp(const Query &a, const Query &b)
int al = bi[a.l], ar = bi[a.r];
int bl = bi[b.l], br = bi[b.r];
if(al != bl)return a.l < b.l;
if(ar != br)return a.r < b.r;
return a.t < b.t;
void add(int x, int& res)
if(cnt[x] == 0)res ++ ;
cnt[x] ++ ;
void del(int x, int& res)
cnt[x] -- ;
if(cnt[x] == 0)res -- ;
int main()
n = read(), m = read();
for(register int i = 1; i <= n; ++ i) w[i] = read();
int mq = 0, mc = 0;
for(register int i = 1; i <= m; ++ i)
char op[2];
int l, r;
scanf("%s", op);
l = read(), r = read();
if(op[0] == 'Q')
q[ ++ mq] = (Query)mq, l, r, mc;
else
c[ ++ mc] = (Modify)l, r;
//这里block一定要加1,可能出现0的情况导致除0发生浮点错误
//block=ceil(exp((log(n)+log(mc))/3));//分块大小
//block = cbrt(n * mc);
//block = pow(n * n, 1.0 / 3);
//block = pow(n, 2.0 / 3);
for(int i = 1; i <= n; ++ i)
bi[i] = (i - 1) / block;
sort(q + 1, q + 1 + mq, cmp);
for(register int k = 1, i = 0, j = 1, t = 0, res = 0; k <= mq; ++ k)
int id = q[k].id, l = q[k].l, r = q[k].r, tim = q[k].t;
//先处理x轴
/*
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(i < r)res += ++ cnt[w[ ++ i]] 莫队 + 带修莫队