莫队算法

机房的众神犇都在搞这个东西,本SB也掺和一下下吧。

莫队算法可用于解决一类可离线且在得到区间\([l,r]\)的答案后,能在\(O(1)\)\(O(\log_2{n})\)得到区间\([l,r+1]\)\([l-1,r]\)的答案的问题

先看这样一个问题:

给出n个数字,m次询问,每次询问在区间\([l_i,r_i]\)之间任选两个数字相等的概率是多少。(n,q<=50000)(小z的袜子)

在区间\([l,r]\)中,这个概率是:
\[\frac{\sum_{i=1}^{v}C(2,f(i))}{C(2,r-l+1)}\] (v表示数字值,f(i)表示数字i在区间内出现的次数)

由于没有加和性质,传统的线段树什么的完全派不上用场了呢!

考虑分子,因为\(C(2,x)=\frac{x^2-x}{2}\),所以分子=\(\frac{\sum_{i=1}^{v}f(i)^2-\sum_{i=1}^{v}f(i)}{2}\)
显然 \(\sum_{i=1}^{v}f(i)=r-l+1\)

若得知区间\([l,r]\)的答案怎么求区间\([l,r+1]\)的答案呢?仔细想想。恩,有了。区间\([l,r+1]\)与区间\([l,r]\)相比只多了一个元素Z,这种改动是很小的,那么前式中分子的值\(S=S_0-f(Z)^2+(f(Z)+1)^2-1=S_0+2*f(Z)\),同时++f(z),恩,\(O(1)\)的。这样的话,在处理下一个询问\([l_i,r_i]\)时,复杂度就是\(O(|r-r_i|+|l-l_i|)\)的。同样的方法,也可以在\(O(1)\)内求出\([l-1,r]\)\([l+1,r]\),\([l,r-1]\)。这样的方法对于随机数据表现是很好的,但也不难给出故意卡你的数据。

这时,就需要莫队算法来撑腰了,这也是莫队算法优化的精髓。

注意到,每个区间可以抽象成平面中的点,每次转移的花费都相当与从某点到另一点的曼哈顿距离的长度。恩,所以呢?

所以我们花费的便是这些平面中的点联通的曼哈顿距离。平面点的曼哈顿最小生成树!

对!但平面点的曼哈顿最小生成树怎么求呢?枚举两两点连接\(O(n^2)\),毫无意义。其实平面点的曼哈顿最小生成树有基于平面区域划分的\(O(nlog_2n)\)的求法,但我们有更简洁的方法。对,分块!

神犇曰:分块是个好东西

确实,利用分块,我们可以实现\(O(n\sqrt{n})\)的时间复杂度。虽然求解平面点的曼哈顿最小生成树是\(O(nlog_2n)\)的,但根据莫队论文中的证明,用到这里时,仍然是\(O(n\sqrt{n})\),只不过常数小一些罢了。

分块的做法:
\(x=\sqrt(n)\),以\([1,x],[x+1,2x],[2x+1,3x]...\)分块
用pos数组维护端点i在第pos[i]块中,然后就搞呗。

这样搞:

1):排序,以左段点所在的块为第一关键字,以右端点为第二关键字

2):从左往右处理询问(离线)

3):不断调整l,r的位置并同时修改

时间复杂度证明:

右端点移动:
首先我们考虑一个块里面的转移情况
由于一个块里面的询问都按右端点排序
所以我们右端点在一个块里面最多移动n次
\(O(\sqrt{n})\)个块,那么同一个块内的右端点移动最多就是\(O(n\sqrt{n})\)
然后考虑从一个块到另一个块导致的右端点变化
最坏情况,右端点由n到1,那么移动n次
\(O(\sqrt{n})\)个块
那么从一个块到另一个块的事件只会发生\(O(\sqrt{n})\)次……
所以这种右端点移动的次数也是\(O(n\sqrt{n})\)
没有别的事件导致右端点移动了
左端点移动:
同一个块里面,由于左端点都在一个长度为\(O(\sqrt{n})\)的区间里面
所以在同一块里面移动一次,左端点最多变化\(O(\sqrt{n})\)
总共有n个询问……
所以同一块里面的移动最多n次
那么同一个块里面的左端点变化最多是\(O(n\sqrt{n})\)
考虑跨越块
每由第i个块到第i+1个块,左端点最坏加上\(O(\sqrt{n})\)
总共能加上\(O(\sqrt{n})\)
所以跨越块导致的左端点移动是\(O(n)\)
综上,分块做法是\(O(n*\sqrt{n})\)

总结

莫队算法在解决离线区间询问几乎是无敌的。
恩,几乎只要能离线,用分块的莫队算法都能取得一个令人满意的的解法。
所以就有很多扩展(解决线段树等数据结构由于需要区间加和性而不能解决的问题),如区间众数,平均数什么的。
恩。棒!

附:
[BZOJ]2038 小Z的袜子 分块 莫队算法

#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

const int maxn = 50000 + 500;
typedef long long LL;

LL gcd(LL a,LL b)
{
    return (b==0)?a:gcd(b,a%b);
}

int pos[maxn];
int col[maxn];
int f[maxn];
int n,m;

struct Query
{
    int l,r,id;
    LL a,b;
    friend bool operator < (const Query &R,const Query &T)
    {
        return pos[R.l]<pos[T.l] || (pos[R.l]==pos[T.l] && R.r<T.r);
    }
    void modify()
    {
        LL k=gcd(a,b);
        a/=k,b/=k;
    }
}Q[maxn];
bool cmp_id(const Query &a,const Query &b)
{
    return a.id<b.id;
}

void init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&col[i]);
    int limit=(int)sqrt((double)n+0.5);
    for(int i=1;i<=n;++i)
        pos[i]=(i-1)/limit+1;//左端点分块
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&Q[i].l,&Q[i].r);
        Q[i].id=i;
    }
    sort(Q+1,Q+m+1);
}

void modify(int p,LL &ans,int add)
{
    ans=ans+2*add*f[col[p]]+1;
    f[col[p]]+=add;
}

void solve()
{
    LL ans=0;
    int l=1,r=0;
    for(int i=1;i<=m;++i)
    {
        if(r<Q[i].r)
        {
            for(r=r+1;r<Q[i].r;++r)
                modify(r,ans,1);
            modify(r,ans,1);
        }
        if(Q[i].l<l)
        {
            for(l=l-1;Q[i].l<l;--l)
                modify(l,ans,1);
            modify(l,ans,1);
        }
        if(Q[i].r<r)
            for(;Q[i].r<r;--r)
                modify(r,ans,-1);
        if(l<Q[i].l)
            for(;l<Q[i].l;++l)
                modify(l,ans,-1);
        if(Q[i].l==Q[i].r)
        {
            Q[i].a=0,Q[i].b=1;
            continue;
        }
        Q[i].a=ans-(Q[i].r-Q[i].l+1),Q[i].b=(LL)(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l);
        Q[i].modify();
    }
    sort(Q+1,Q+m+1,cmp_id);
    for(int i=1;i<=m;++i)
        printf("%lld/%lld\n",Q[i].a,Q[i].b);
}

int main()
{
    init();
    solve();

    return 0;
}

Refrence:
http://foreseeable97.logdown.com/posts/158522-233333

http://ydcydcy1.blog.163.com/blog/static/21608904020134411543898/

http://vawait.com/manhattanmst/

http://blog.csdn.net/huzecong/article/details/8576908

转载于:https://www.cnblogs.com/hzf-sbit/p/4056874.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
莫队算法是一种基于分块的算法,用于解决一些静态区间查询问题,时间复杂度为 $O(n\sqrt{n})$。以下是一个基于Python的莫队算法的示例代码: ```python import math # 定义块的大小 BLOCK_SIZE = 0 # 初始化块的大小 def init_block_size(n): global BLOCK_SIZE BLOCK_SIZE = int(math.sqrt(n)) # 定义查询操作 def query(left, right): pass # 在这里写查询操作的代码 # 定义添加操作 def add(x): pass # 在这里写添加操作的代码 # 定义删除操作 def remove(x): pass # 在这里写删除操作的代码 # 定义莫队算法 def mo_algorithm(n, q, queries): init_block_size(n) queries.sort(key=lambda x: (x[0] // BLOCK_SIZE, x[1])) left, right = 0, -1 for query in queries: while left > query[0]: left -= 1 add(left) while right < query[1]: right += 1 add(right) while left < query[0]: remove(left) left += 1 while right > query[1]: remove(right) right -= 1 query(query[0], query[1]) ``` 在这段代码中,我们首先定义了一个全局变量 `BLOCK_SIZE`,用于表示块的大小。接着,我们定义了三个操作函数 `query()`、`add()` 和 `remove()`,分别用于查询、添加和删除元素。在 `mo_algorithm()` 函数中,我们首先调用 `init_block_size()` 函数初始化块的大小,然后将查询操作按照块的大小和右端点排序,接着使用双指针维护当前查询区间的左右端点,每次移动指针时调用 `add()` 和 `remove()` 函数更新块的状态,最后调用 `query()` 函数进行查询操作。 请注意,这段代码只是一个示例,具体的 `query()`、`add()` 和 `remove()` 函数的实现取决于具体的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值