根据上周的规划,本周主要是看莫队算法,从大佬的博客里看到莫队的前置算法,所以最开始两天就先复习了LCA,倍增树,并且着重看了一下分块算法再开始学习。
莫队算法作是一种代码简单实用性强的解决区间问题的算法,通过优雅的暴力实现O(n sqrt(n))的复杂度。利用操作两个指针,在输入区间的时候只移动指针,
(一个很多博客都出现的板子题)这个题本质就是预处理之后进行一个分块,优化暴力枚举, 这个大佬的博客的图解非常详细
#include <bits/stdc++.h>
using namespace std;
#define maxn 1000007
#define maxb 1007
int aa[maxn], cnt[maxn], belong[maxn];
int n, m, size, bnum, now, ans[maxn];
struct point
{
int l, r, id;
} q[maxn];
int cmp(point a, point b)
{
return belong[a.l] == belong[b.l] ? belong[a.l] < belong[b.l] : ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r);
}
#define isdigit(x) ((x) >= '0' && (x) <= '9') //快读,抄板子。。。
int read()
{
int res = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
res = (res << 1) + (res << 3) + c - 48, c = getchar();
return res;
}
void printi(int x)
{
if (x / 10)
printi(x / 10);
putchar(x % 10 + '0');
}
int main()
{
cin >> n;
double size = sqrt(n);
bnum = ceil((double)n / size); //向上取整
for (int i = 1; i <= bnum; i++) //分块
for (int j = (i - 1) * size + 1; j <= i * size; ++j)
{
belong[j] = i;
}
for (int i = 1; i <= n; i++)
aa[i] = read();
m = read();
for (int i = 1; i <= m; i++)
{
q[i].l = read(), q[i].r = read();
q[i].id = i;
}
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
for (int i = 1; i <= m; i++) //开始莫队,将add与del结合,优化算法
{
int ql = q[i].l, qr = q[i].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--]];
ans[q[i].id] = now;
}
for (int i = 1; i <= m; i++)
{
printi(ans[i]);
cout << endl;
}
return 0;
}
由于莫队算法里加了sort所以必须离线,但是在一些可以离线的带修改题目中莫队也可以使用,就是再加入一个指针t(P1903 [国家集训队] 数颜色 / 维护队列),这篇luogu题解里有理解多加t维度的图,做的还比较形象。说实话多加一个t在代码上其实并没有有太大的改动,但主要是理解方面移动的方向变成六个。
另外还有树上莫队,类似给定一个n个节点的树,然后求u到v的路径上有多少个不同的整数这种,并且不带修改就可以考虑使用莫队算法,利用DFS或者欧拉序的方法将树转为序列。但是这个操作我觉挺不好理解,包含欧拉序与莫队还要利用欧拉序找LCA,这么一杂糅就难理解了。。。在看题解的时候有大佬写的博客写的挺详细,要常看一看。
下周继续强化莫队算法,把大佬博客里推荐的基础题做一做,这几天从SPOJ搞了几道做,板子用的倒是熟练。。然后浅看看后面主席树的内容。