无修莫队
无修的莫队算法,是用来处理没有修改操作的序列的询问操作的离线算法
莫队算法的本质是什么?
大概就是分块加上一个大家可能都想到过的东西
也就是一个区间1l-r,其左边的区间2 (l - 1) - (r - 1)的和转化到区间1的和只需要O(1)的时间复杂度
也就是
s
u
m
1
=
s
u
m
2
+
a
[
r
]
−
a
[
l
−
1
]
sum1 = sum2 + a[r] - a[l - 1]
sum1=sum2+a[r]−a[l−1]
如果前面并没有提到分块,可能大家就会想到一个
O
(
n
2
)
O(n^2)
O(n2)的算法
那么这个算法我就不提了。。
那如果有分块的加入呢?
我们的思路是这样的:
将一个长度为n的序列分成
s
q
r
t
(
n
)
sqrt(n)
sqrt(n)块
然后对询问进行排序 如果两个询问的左端点在同一块,则比较两个询问的右端点,小的在前
如果左端点不在同一块,则比较两个块的前后顺序,块在前面的在前
这样就可以优化上述处理询问方法的时间复杂度,具体看下面的分析
根据之前提到的查询方法,可以得出以下代码:
int x = m[k].l, y = m[k].r;//m是询问
while (l > x) ins( -- l );
while (l < x) del( l ++ );
while (r > y) del( r -- );
while (r < y) ins( ++ r );
时间复杂度
这里设
s
u
m
=
s
q
r
t
(
n
)
sum = sqrt(n)
sum=sqrt(n)
每个块长度为
l
e
n
=
n
/
s
u
m
len = n / sum
len=n/sum
枚举了
k
k
k次答案
对于l
若两个l在同一块,则最坏情况为
O
(
l
e
n
)
O(len)
O(len)
若两个l在不同块,则最坏情况为
O
(
2
∗
l
e
n
)
O(2 * len)
O(2∗len)
则枚举k次的时间复杂度为
O
(
k
∗
l
e
n
)
O(k * len)
O(k∗len)
对于r
由于两个l不一定在同一块,所以r时不一定有序的
也就是说,每个r的移动都有可能带来
O
(
n
)
O(n)
O(n)的时间复杂度
由于要枚举的是sum个块,所以时间复杂度为
O
(
n
∗
s
u
m
)
O(n * sum)
O(n∗sum)
整体
O ( k ∗ l e n + n ∗ s u m ) = O ( n ∗ s u m + n ∗ s u m ) = O ( n ∗ s u m ) = O ( n ∗ s q r t ( n ) ) O(k* len + n * sum) = O(n * sum + n * sum) = O(n * sum) = O(n * sqrt(n) ) O(k∗len+n∗sum)=O(n∗sum+n∗sum)=O(n∗sum)=O(n∗sqrt(n))
看来是个非常优秀的算法
例题
luogu P1972
这道题已经非常裸了,解析就不给出了,自己看代码就好
(luogu加强了数据无法再AC,但是写一写还是没什么问题的)
#include <bits/stdc++.h>
using namespace std;
const int max_l = 10000005;
struct hazaking
{
int l, r, p, id, ans;
}m[max_l];
int a[max_l], vis[max_l];
inline bool cmp1(hazaking a, hazaking b)
{
return a.id ^ b.id ? a.id < b.id : a.r < b.r;
}
inline bool cmp2(hazaking a, hazaking b)
{
return a.p < b.p;
}
int read()
{
int sum = 0 , flag = 1;
char c = getchar();
for (; c < '0' || c > '9' ; c = getchar() )
if ( ! (c ^ '-') ) flag = -1;
for (; c >= '0' && c <= '9'; c = getchar() )
sum = (sum << 3 ) + (sum << 1) + (c ^ 48);
return sum * flag;
}
int n = read();
int len = sqrt ( n );
int q;
int l = 1 , r = 0;
int ans = 0;
inline void ins(int k)
{
if ( ! vis [ a[ k ] ] ++ ) ans ++ ;//访问到则增加标记
}
inline void del(int k)
{
if ( ! -- vis [ a[ k ] ] ) ans -- ;//访问到则减少标记
}
inline void query(int k)
{
int x = m[k].l , y = m[k].r ;
while (l > x) ins( -- l );
while (l < x) del( l ++ );
while (r > y) del( r -- );
while (r < y) ins( ++ r );//四句查询语句
m[k].ans = ans;//这里还可以用记录的询问id储存答案,无需后面的排序
}
inline void init()
{
for (int i = 1 ; i <= n ; i++)
a[i] = read();
q = read();
for (int i = 1 ; i <= q; i ++)
{
m[i].id = ( m[i].l = read() ) / len ;
m[i].r = read();
m[i].p = i;
}//对于询问数组初始化
sort(m + 1, m + q + 1, cmp1 );//对数组进行排序
for (int i = 1 ; i <= q ; i++)
query(i);//查询
sort(m + 1, m + q + 1, cmp2 );//对答案排序,满足数据输入顺序
for (int i = 1 ; i <= q ; i ++)
printf("%d\n", m[i].ans );//输出答案
}
inline void work()
{
}
int main()
{
init();
work();
return 0;
}