莫队算法整理
给出n个数,给出m个询问,每个询问要求输出区间[l,r]之间的数字的种类。(1 <= n , m , ai <= 1e5)
如果题目不要求强制在线的话,这样的题目可以考虑使用莫队算法。(注意数字的取值范围,如果数字是[1,1e9]那就不可以了)
首先拿到这道题目可以想到的最暴力的解法,遍历所有区间[l,r],用cnt数组记录每个数组出现的次数,最后统计出现数字的种类,这样的时间复杂度就是O(m * (n + MAXai)),肯定是过不了题的。
然后莫队算法这种通过优美优化之后的暴力算法就能解决这种问题,莫队算法就是通过分块思想,将查询区间进行排序,将时间复杂度优化到O(n * sqrt(n))
既然需要排序,那么需要了解莫队是怎么排序的:
首先将整个序列分为sqrt(n)块,每块长度为sqrt(n),并按照1到sqrt(n)进行标号。这里假设len = sqrt(n)。然后向询问的区间进行排序,以区间左端点所在的块的序号进行排序,如果所在块的需要相同,则再按照右端点从小到大进行排序。
然后分析一下所需要的时间复杂度:
(1)sort的时间复杂度:n * log(n)
(2)因为区间左端点是按照块的序号进行排序的,而块内的左端点实际上是无序的,这里考虑他的最大时间复杂度,即每一次移动都从块的一端移动到另一端,那么m个端点所需要的复杂度就是O(m * len)。
(3)在区间左端点处于同一块的时候,区间的右端点是有序的,同样考虑他的最大时间复杂度,即每一次移动都从序列的最左端移动到序列的最右端,因为一共有len个块,所需要的复杂度就是O(len * n)
这样一来总的时间复杂度就优化到了O(n * log(n) + m * sqrt(n) + n * sqrt(n)) = O(n * sqrt(n))
标准的排序代码
int cmp(node& a, node& b) { return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : a.r < b.r; }
但是当实际运用莫队算法的时候,因为莫队算法的复杂度本身就是O(n * sqrt(n))在一些情况下,极其容易被卡常数。
这里就需要许多优(shen)美(xian)的优化
1.莫队奇偶性排序
(这个排序真的是优(shen)美(xian))
最最最最重要的优化!!!
看似没有用,但是对于每个块的查询都可以减少很多时间
int cmp(node& a, node& b) { return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r); }
对于奇数块右端点从小到大排序,偶数块右端点从大到小排序。
原理其实很简单:右指针跳完奇数块,往回跳的时候,顺便把偶数块跳完,然后再跳下一块奇数块
2.移动指针的压缩
就是把这部分
void add(int pos) { if(!cnt[aa[pos]]) ++now; ++cnt[aa[pos]]; } void del(int pos) { --cnt[aa[pos]]; if(!cnt[aa[pos]]) --now; }
和这部分
while(l < ql) del(l++); while(l > ql) add(--l); while(r < qr) add(++r); while(r > qr) del(r--);
根据运算符的优先级生生压缩成
while(l < ql) now -= !--cnt[aa[l++]]; while(l > ql) now += !cnt[aa[--l]]++; while(r < qr) now += !cnt[aa[++r]]++; while(r > qr) now -= !--cnt[aa[r--]];
(垃圾函数调用,我选择inline)
3.究极开O2 #pragma GCC optimize(2)
如果实在被卡到落泪,然后又写不出其他算法可以一试(当然如果比赛已经把这个禁了,那就不要增加罚时了)
这里给出一些简单的例题
1.SP3267 DQUERY - D-query
https://www.luogu.com.cn/problem/SP3267
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #define isdigit(x) ((x) >= '0' && (x) <= '9') #define maxn 1000010 using namespace std; int read() { int X = 0, w = 0; char ch = 0; while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); } while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar(); return w ? -X : X; } int n, m; int arr[maxn], cnt[maxn], res = 0 , pos[maxn] , ans[maxn]; int size, bnum; struct node { int l, r ,id; }q[maxn]; int cmp1(node & a, node & b) { return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r); } void add(int x) { if (!cnt[arr[x]]) ++res; ++cnt[arr[x]]; } void del(int x) { --cnt[arr[x]]; if (!cnt[arr[x]]) --res; } void printi(int x) { if(x / 10) printi(x / 10); putchar(x % 10 + '0'); } int main() { scanf("%d", &n); int size = sqrt(n); int bnum = ceil((double)n / size); for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / sqrt(n) + 1; for (int i = 1; i <= n; ++i) { scanf("%d", &arr[i]); } m = read(); for (int i = 1; i <= m; ++i) { scanf("%d %d", &q[i].l, &q[i].r); q[i].id = i; } sort(q + 1, q + 1 + m, cmp1); int l = 1, r = 0; for (int i = 1; i <= m; ++i) { int qr, ql; ql = q[i].l, qr = q[i].r; while (l < ql) del(l++); while (l > ql) add(--l); while (r > qr) del(r--); while (r < qr) add(++r); ans[q[i].id] = res; } for (int i = 1; i <= m; ++i) { printi(ans[i]), putchar('\n'); } return 0; }
建议手动交C++11,被CE到哭
2.P2709 小B的询问
https://www.luogu.com.cn/problem/P2709
#include<cstdio> #include<string.h> #include<algorithm> #include<cmath> #include<iostream> #include<vector> #include<queue> #include<set> #include<map> #include<cctype> #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) #define mem(a,x) memset(a,x,sizeof(a)) #define lson rt<<1,l,mid #define rson rt<<1|1,mid + 1,r #define P pair<int,int> #define ull unsigned long long using namespace std; typedef long long ll; const int maxn = 5e5 + 10; const ll mod = 998244353; const int inf = 0x3f3f3f3f; const long long INF = 0x3f3f3f3f3f3f3f3f; const double eps = 1e-7; inline ll read() { ll X = 0, w = 0; char ch = 0; while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); } while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar(); return w ? -X : X; } ll n, m, k; ll arr[maxn], pos[maxn], res = 0, cnt[maxn] = {0}, ans[maxn]; struct node { int l, r, id; }q[maxn]; int cmp(node& a, node& b) { return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r); } void add(int x) { ++cnt[arr[x]]; res += (cnt[arr[x]] * cnt[arr[x]] - (cnt[arr[x]] - 1) * (cnt[arr[x]] - 1)); } void del(int x) { --cnt[arr[x]]; res -= ((cnt[arr[x]] + 1) * (cnt[arr[x]] + 1) - cnt[arr[x]] * cnt[arr[x]]); } int main() { n = read(), m = read(), k = read(); for (int i = 1; i <= n; ++i) { pos[i] = (i - 1) / sqrt(n) + 1; } for (int i = 1; i <= n; ++i) { arr[i] = read(); } for (int i = 1; i <= m; ++i) { q[i].l = read(), q[i].r = read(); q[i].id = i; } //cout << "debug" << endl; sort(q + 1, q + 1 + m, cmp); int l = 1, r = 0; for (int i = 1; i <= m; ++i) { int ql = q[i].l, qr = q[i].r; //cout << ql << " " << qr << endl; while (l < ql) del(l++); //cout << res << endl; while (l > ql) add(--l); //cout << res << endl; while (r > qr) del(r--); //cout << res << endl; while (r < qr) add(++r); //cout << res << endl; ans[q[i].id] = res; } for (int i = 1; i <= m; ++i) cout << ans[i] << endl; return 0; }
3.P3709 大爷的字符串题
https://www.luogu.com.cn/problem/P3709
4.P4074 [WC2013]糖果公园
https://www.luogu.com.cn/problem/P4074
5.P1903 [国家集训队]数颜色 / 维护队列(带修改的莫队)
https://www.luogu.com.cn/problem/P1903
莫队扩展——树上莫队(子树统计(dfs序应用),和路径统计(欧拉序应用))、回滚莫队
待补充....