离线莫队算法
离线莫队算法用于解决区间查询问题,核心思想是,如果我们已知区间 [ l , r ] [l,r] [l,r]内的答案,如果我们能在 O ( 1 ) O(1) O(1)时间内转移到 [ l , r + 1 ] [l,r+1] [l,r+1], [ l , r − 1 ] [l,r-1] [l,r−1], [ l − 1 , r ] [l - 1,r] [l−1,r]或者 [ l + 1 , r ] [l + 1,r] [l+1,r]即相邻区间内,那么我们就可以通过这种区间转移的方式求出所有区间的答案,其核心还是尽可能的利用已知,而不是去重复求解。
区间转移
我们设计双指针算法,利用 l l l和 r r r变量标记区间的端点(闭区间),首先初始化令 l = 1 , r = 1 l=1,r=1 l=1,r=1, a n s = f ans=f ans=f, f f f为区间 [ 1 , 1 ] [1,1] [1,1]的答案。之后我们便可以进行区间转移。
我们遍历每一个询问,然后将 l r lr lr移动到目的地,这里必须先扩大区间长度,然后再缩小区间长度,必须是这个顺序,否则 l r lr lr指针会出现交叉。
for (int i = 0; i < m; i++)
{
Query curr = q[i];
while (l > curr.l)
{
l--;
// add l
}
while (r < curr.r)
{
r++;
// add r
}
while (l < curr.l)
{
// delete l
l++;
}
while (r > curr.r)
{
// delete r
r--;
}
ans[curr.id] = sum;
}
离线查询与区间分块
如果我们按照题目给出的区间查询顺序进行区间转移,那时间复杂度将是巨大的,例如给出 [ 1 , 3 ] [1,3] [1,3], [ 888 , 999 ] [888,999] [888,999], [ 1 , 4 ] [1,4] [1,4],如果我们按照这个顺序查询,那么我们必须经过两次的大转移,如果我们将 [ 1 , 3 ] [1,3] [1,3]和 [ 1 , 4 ] [1,4] [1,4]放在一起的话,我们只需要经过一次大转移即可,我们应该对查询进行离线,然后排序以降低时间复杂度。
首先我们对区间进行分块,给每一个区间内的点分配一个所在区间的编号,代码如下:
int q_n = sqrt(n) + 1;
for (int i = 1; i <= n; i++)
{
scanf("%lld", arr + i);
block[i] = i / q_n;
}
其中 n n n为区间大小。我们先以块大小为 n \sqrt{n} n进行分块,块的数量为 n \sqrt{n} n。
之后我们对查询进行排序,排序规则是以 l l l所在的块编号为第一关键字, r r r为第二关键字。
struct Query
{
int l;
int r;
int id;
bool operator<(const Query &o) const
{
if (block[l] != block[o.l])
{
return block[l] < block[o.l];
}
else
{
return r < o.r;
}
}
} q[50005];
排序结束后,我们按照排序的结果进行区间转移即可。
奇偶排序
有时候我们对于相邻区间的 r r r进行交叉排序,如:
struct Query
{
int l;
int r;
int id;
bool operator<(const Query &o) const
{
if (block[l] != block[o.l])
{
return block[l] < block[o.l];
}
else if (block[l] & 1)
{
return r < o.r;
}
else
{
return r > o.r;
}
}
} q[50005];
这样我们就能很好的处理,折返的情况,
有时候也存在负优化的情况,具体情况具体分析。
所以说想卡莫队非常容易,我们只需要让 r r r变化幅度大一些,即可。
例题
#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
typedef long long ll;
using namespace std;
int block[50005];
ll arr[50005];
ll cnt[50005];
ll ans[50005];
int n, m, k;
struct Query
{
int l;
int r;
int id;
bool operator<(const Query &o) const
{
if (block[l] != block[o.l])
{
return block[l] < block[o.l];
}
else if (block[l] & 1)
{
return r < o.r;
}
else
{
return r > o.r;
}
}
} q[50005];
int main()
{
scanf("%d %d %d", &n, &m, &k);
int q_n = sqrt(n) + 1;
for (int i = 1; i <= n; i++)
{
scanf("%lld", arr + i);
block[i] = i / q_n;
}
for (int i = 0; i < m; i++)
{
int l, r;
scanf("%d %d", &l, &r);
q[i].l = l;
q[i].r = r;
q[i].id = i;
}
sort(q, q + m);
int l = 1, r = 1;
ll sum = 1;
cnt[arr[1]]++;
for (int i = 0; i < m; i++)
{
Query curr = q[i];
while (l > curr.l)
{
l--;
sum += 2 * cnt[arr[l]] + 1;
cnt[arr[l]]++;
}
while (r < curr.r)
{
r++;
sum += 2 * cnt[arr[r]] + 1;
cnt[arr[r]]++;
}
while (l < curr.l)
{
sum += -2 * cnt[arr[l]] + 1;
cnt[arr[l]]--;
l++;
}
while (r > curr.r)
{
sum += -2 * cnt[arr[r]] + 1;
cnt[arr[r]]--;
r--;
}
ans[curr.id] = sum;
}
for (int i = 0; i < m; i++)
{
printf("%lld\n", ans[i]);
}
return 0;
}
带修改莫队
记录时间为第三维,即 [ l , r , t i m e ] [l,r,time] [l,r,time]在三个维度之间转换,分块对 l l l和 r r r进行分块,分块大小为 n t 3 \sqrt[3] {nt} 3nt,当 t = 0 t=0 t=0的时候,分块大小为 n \sqrt{n} n
#include <bits/stdc++.h>
using namespace std;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
typedef long long ll;
int n, m;
int arr[133335];
int block;
#define PT(x) (x / block)
struct Modification
{
int last;
int idx;
int col;
} mod[133335];
struct Query
{
int time;
int l, r;
int idx;
bool operator<(const Query &o) const
{
if (PT(l) != PT(o.l))
return PT(l) < PT(o.l);
if (PT(r) != PT(o.r))
return PT(r) < PT(o.r);
return time < o.time;
}
} que[133335];
int ti = 0;
int id = 0;
int cnt[1000005];
int Ans[133335];
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", arr + i);
}
for (int i = 0; i < m; i++)
{
char op;
int a, b;
scanf("\n%c%d%d", &op, &a, &b);
if (op == 'Q')
{
id++;
que[id].l = a;
que[id].r = b;
que[id].time = ti;
que[id].idx = id;
}
else
{
ti++;
mod[ti].idx = a;
mod[ti].last = arr[a];
mod[ti].col = b;
arr[a] = b;
}
}
for (int i = ti; i > 0; i--)
{
arr[mod[i].idx] = mod[i].last;
}
if (ti == 0)
{
block = ceil(sqrt(n));
}
else
{
block = ceil(exp((log(n) + log(ti)) / 3));
}
sort(que + 1, que + id + 1);
int l = 0, r = 0, t = 0;
int ans = 0;
for (int i = 1; i <= id; i++)
{
while (l > que[i].l)
{
l--;
ans += !cnt[arr[l]];
cnt[arr[l]]++;
}
while (r < que[i].r)
{
r++;
ans += !cnt[arr[r]];
cnt[arr[r]]++;
}
while (l < que[i].l)
{
cnt[arr[l]]--;
ans -= !cnt[arr[l]];
l++;
}
while (r > que[i].r)
{
cnt[arr[r]]--;
ans -= !cnt[arr[r]];
r--;
}
while (t > que[i].time)
{
if (mod[t].idx >= l && mod[t].idx <= r)
{
cnt[arr[mod[t].idx]]--;
ans -= !cnt[arr[mod[t].idx]];
}
arr[mod[t].idx] = mod[t].last;
if (mod[t].idx >= l && mod[t].idx <= r)
{
ans += !cnt[arr[mod[t].idx]];
cnt[arr[mod[t].idx]]++;
}
t--;
}
while (t < que[i].time)
{
t++;
if (mod[t].idx >= l && mod[t].idx <= r)
{
cnt[arr[mod[t].idx]]--;
ans -= !cnt[arr[mod[t].idx]];
}
arr[mod[t].idx] = mod[t].col;
if (mod[t].idx >= l && mod[t].idx <= r)
{
ans += !cnt[arr[mod[t].idx]];
cnt[arr[mod[t].idx]]++;
}
}
Ans[que[i].idx] = ans;
}
for (int i = 1; i <= id; i++)
{
printf("%d\n", Ans[i]);
}
return 0;
}