莫队算法 无修

本文介绍了无修改操作的序列询问处理的离线算法——无修莫队算法。该算法通过分块策略,将时间复杂度优化到O(n*sqrt(n))。内容包括算法本质、时间复杂度分析及例题解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无修莫队

无修的莫队算法,是用来处理没有修改操作的序列的询问操作的离线算法
莫队算法的本质是什么?
大概就是分块加上一个大家可能都想到过的东西
也就是一个区间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[l1]
如果前面并没有提到分块,可能大家就会想到一个 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(2len)
则枚举k次的时间复杂度为 O ( k ∗ l e n ) O(k * len) O(klen)

对于r

由于两个l不一定在同一块,所以r时不一定有序的
也就是说,每个r的移动都有可能带来 O ( n ) O(n) O(n)的时间复杂度
由于要枚举的是sum个块,所以时间复杂度为 O ( n ∗ s u m ) O(n * sum) O(nsum)

整体

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(klen+nsum)=O(nsum+nsum)=O(nsum)=O(nsqrt(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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值