zoukankan      html  css  js  c++  java
  • 让菜鸡讲一讲莫队(非修改)

    传说中,莫队算法能解决一切区间处理问题

    这是一个优雅的暴力

    那么我们先看一道题


    蛤蛤的项链

    HH有一串由各种漂亮的贝壳组成的项链。

    HH相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

    HH不断地收集新的贝壳,因此,他的项链变得越来越长。

    有一天,他突然提出了一个问题:

    某一段贝壳中,包含了多少种不同的贝壳?

    这个问题很难回答……因为项链实在是太长了。

    于是,他只好求助睿智的你,来解决这个问题。

    这一题有多组询问。


    之前我们解决区间问题的时候常用的一种方法是种一棵线段树

    但是现在这东西不满足加和性质,线段树不能用了!

    其实要用也是可以的,只不过有很大的几率即使不TLE也会MLE

    怎么办?

    那么我们祭出我们的莫队算法


    莫队的组成部分

    首先,莫队有一对指针,现在我们把它们分别叫做l,r

    一开始,它们在序列中像这样摆放:

     l
     1234352435142435123   <贝壳颜色v>
    r
    

    然后,莫队有一排计数君cnt[]

    它们统计在l~r区间中每种颜色出现了多少次

    如果一开始不把l放在r前面的话,那么cnt[]将不能很好的反映出l~r区间的颜色情况

    具体为什么请自行脑补

    当l,r移动的时候,

    cnt[]的值可以很方便的作出修改

    比如现在

    l=2
    r=5
    cnt={0,0,1,2,1,0}
    
      l
     1234352435142435123   <v>
         r
    

    如果我们要得到l=1,r=6的情况

    那么我们可以把l向左移动,把r向右移动

    l--,cnt[v[l]]++;r++,cnt[v[r]]++;
    //简写为cnt[v[--l]]++,cnt[v[++r]]++;
    "因为它们是扩张,所以要先移动再计数"
    
    l=1
    r=6
    cnt={0,1,1,2,1,1}
    
     l
     1234352435142435123   <v>
          r
    

    又如果我们要得到l=3,r=4的情况

    那么我们可以把l向右移动,把r向左移动

    cnt[v[l]]--,l++;cnt[v[r]]--,r--;
    //简写为--cnt[v[l++]],--cnt[v[r--]];
    "因为它们是缩小,所以要先计数再移动"
    
    l=3
    r=4
    cnt={0,0,1,1,0,0}
    
       l
     1234352435142435123   <v>
        r
    

    莫队还有一个统计答案sum

    可以在区间修改的时候顺便修改它

    比如现在

    l=2
    r=3
    sum=2
    cnt={0,0,1,1,0,0}
    
      l
     1234352435142435123   <v>
       r
    

    当我们要把l向右移1格的时候,

    颜色2的出现次数减少了1,

    而由于减少后2的出现次数变成了0,那么sum就要减少1

    if(--cnt[v[l++]]==0)sum--;
    if(--cnt[v[r--]]==0)sum--;//同理
    

    而当我们要把r向右移1格的时候,

    颜色4的出现次数增加了1,

    而由于增加前4还没有出现过,那么sum就要增加1

    if(cnt[v[++r]]++==0)sum++;
    if(cnt[v[--l]]++==0)sum++;//同理
    

    有没有想过,l和r这么移来移去是为了什么?

    如果这道题只有1次询问,我们大可以暴力。

    而莫队这样做,是为了处理多组询问。

    每一次询问都可以在上次询问的基础上通过移动l,r来回答。

    辣么可见这个算法的复杂度就由它们的移动次数来决定。

    如何减少l,r的移动次数

    显然对于两次询问L,R和L′,R′,

    知道了L,R的答案,

    就可以暴力计算|L−L′|+|R−R′|次得出L′,R′的答案。

    Fa♂现|L−L′|+|R−R′|是曼哈顿距离

    把每个询问看作是二维平面上的点,那么我们的最小总时间,

    就是这些点的最小曼哈顿距离生成树,

    按照这个树的顺序做,复杂度变成了(O(nsqrt n))

    不过这样是不是有点繁琐?

    分块大法好!

    把整个序列分块,把L所在的块为第一关键字,R为第二关键字排序。

    为什么要分块不能直接排呢?

    分块很好的减少了一种情况的影响:

    L<L′<L′′,并且距离很近,但是R′<R<R′′,并且R′与另外两个离得很远,会导致r指针一顿乱跳。

    如果直接按L排序就会浪费非常多的时间。

    由于每个块的大小是(sqrt n)

    分块使得两个询问之间差异平均到了L,R上。

    因此,理论复杂度大约还是是(O(nsqrt n))

    给出蛤蛤的项链的代码,

    这也可以作为非修改莫队的模板。

    #include<bits/stdc++.h>
    using namespace std;
    inline int gotcha()
    {
        register int a=0,b=1,c=getchar();
        while(!isdigit(c))b^=c=='-',c=getchar();
        while(isdigit(c))a=(a<<3)+(a<<1)+c-48,c=getchar();
        return b?a:-a;
    }
    const int _ = 50002 , __ = 2000002 , sect = 667;
    struct trouble
    {
        int l,r,sc,num;
        const int operator < (const trouble &b)const{return sc==b.sc?r<b.r:sc<b.sc;}
    }q[__];
    int n,m,sum=0,cnt[__]={0},col[__],ans[__];
    void add(int a){if(++cnt[a]==1)sum++;}void del(int a){if(--cnt[a]==0)sum--;}
    int main()
    {
        register int i,l=1,r=0;
        for(n=gotcha(),i=1;i<=n;i++)col[i]=gotcha();
        for(m=gotcha(),i=1;i<=m;i++)q[i].l=gotcha(),q[i].r=gotcha(),q[i].sc=q[i].l/sect,q[i].num=i;
        sort(q+1,q+m+1);
        for(i=1;i<=m;i++)
        {
            while(r<q[i].r)add(col[++r]);while(r>q[i].r)del(col[r--]);
            while(l<q[i].l)del(col[l++]);while(l>q[i].l)add(col[--l]);
            ans[q[i].num]=sum;
        }
        for(i=1;i<=m;i++)printf("%d
    ",ans[i]);
        return 0;
    }
    
  • 相关阅读:
    JOISC2020 题解
    Linux系统时钟与硬件时钟
    Excel Application操作指南
    WinCC 7.5 SP1安装方法
    关于WinCC V15.1使用ActiveX的ListView控件时添加失败问题
    WinCC 利用VBScript连接mysql数据库
    WPF中通过AForge实现USB摄像头拍照
    ESP32引脚参考
    C语言创建循环缓冲区(环形缓冲区)-- Circular Buffer(Ring Buffer)
    Android Studio汉化教程
  • 原文地址:https://www.cnblogs.com/finder-iot/p/8409570.html
Copyright © 2011-2022 走看看