码迷,mamicode.com
首页 > 其他好文 > 详细

普通莫队

时间:2020-02-21 14:38:18      阅读:57      评论:0      收藏:0      [点我收藏+]

标签:print   根据   最大   ret   --   基本操作   统计   ios   class   

n个数,区间查询[L,R]出现了几种数字

时间复杂度\(O(n\sqrt n)\)

莫队的基本操作就是把n个数进行分块,每一块有\(\sqrt n\)个,有\(\sqrt n\)块,然后离线查询,把查询进行排序,按照分块位置排序,如果在同一个分块,那么就按照右区间排序,然后对于每一个排序进行暴力遍历即可

  1. 先进行分块,每块有分成\(\sqrt n\)个,bein[j] = i表示第j个数属于第i个分块
for(int i = 1; i <= ceil((double)n/sqrt(n)); i++)
        for(int j = (i - 1) * sqrt(n) + 1; j <= i * sqrt(n); j++)
            bein[j] = i;
  1. 把查询进行离线并排序
struct Query{
    int l,r,id;
}q[maxn];

方法1:先按照查询区域的左区间所处的分块大小排序,如果位与同一个分块,那么久按照右区间排序

bool cmp(Query a,Query b){
    return bein[a.l] == bein[b.l] ? a.r < b.r : bein[a.l] < bein[b.l];
}

方法2:位与同一个奇数块时,按查询右区间进行升序,偶数块时,查询右区间进行降序
在普通排序时,右指针一直往右走,直到最大的右区间,然后再往左走,往左走的一大段路是浪费的,根据下面的优化可以利用好这个左走
那么,如果当前是奇数块,右指针可能到了最右边,当进入下一个分块,也就是偶分块时,那么只需要右指针往左即可

bool cmp1(Query a, Query b) {
    return (bein[a.l] ^ bein[b.l]) ? bein[a.l] < bein[b.l] : ((bein[a.l] & 1) ? a.r < b.r : a.r > b.r);
}
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,cmp1);
  1. 移动左右指针

首先定义l为左指针,r为右指针,

左指针要初始化为1,右指针初始化为0,如果l = 0,那么先l++,a[1]统计1次,r++,a[1]又被统计1次

对于排序后的每一个查询

while(l < q[i].l)del(l++);//l右移
while(l > q[i].l)add(--l);//l左移
while(r < q[i].r)add(++r);//r右移
while(r > q[i].r)del(r--);//r左移

其中now表示当前查询区域不同数值的个数

void add(int x){//加入操作
    if(cnt[a[x]] == 0)now++;//如果a[x]这个数在之前没有出现过,
    ++cnt[a[x]];//这个数出现了一次
}
void del(int x){//删除操作
    --cnt[a[x]];
    if(cnt[a[x]] == 0)--now;//在这次操作后,[l,r]里,a[x]这个数没出现过了
}

那么

ans[q[i].id] = now;//当前查询区间的值就是now了
  1. 输出
for(int i = 1; i <= m; i++)
    printf("%d\n", ans[i]);

关于时间复杂度

在while里面右指针是递增的,所以一个分块每次最多\(O(n)\),\(\sqrt n\)个分块就是\(O(n\sqrt n)\)

在while里面左指针每个查询都在一个分块里移动为\(\sqrt n\),m个查询就是\(O(m\sqrt n)\)

进行分块\(O(n)\)

输入数据\(O(n)\)

离线查询\(O(mlogm)\)

所以是\(O(n\sqrt n)\)

注意

莫队是一个离线算法,不支持在线操作带修改

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 1e6 + 5;
int cnt[maxn],a[maxn];//cnt[i]表示数字i出现的次数,a[]是输入的数
int l = 1,r,now;//l是左指针,r是右指针,now是不同数字的个数
int bein[maxn],ans[maxn];//bein[]分块
void add(int x){//加入操作,右移
    if(cnt[a[x]] == 0)++now;
    ++cnt[a[x]];
}
void del(int x){//删除操作,左移
    --cnt[a[x]];
    if(cnt[a[x]] == 0)--now;
}
struct Query{
    int l,r,id;
}q[maxn];
bool cmp(Query a,Query b){
    return bein[a.l] == bein[b.l] ? a.r < b.r : bein[a.l] < bein[b.l];
}
int cmp1(Query a, Query b) {
    return (bein[a.l] ^ bein[b.l]) ? bein[a.l] < bein[b.l] : ((bein[a.l] & 1) ? a.r < b.r : a.r > b.r);
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1; i <= ceil((double)n/sqrt(n)); i++)
        for(int j = (i - 1) * sqrt(n) + 1; j <= i * sqrt(n); j++)
            bein[j] = i;
    for(int i = 1; i <= n; i++)
        scanf("%d",&a[i]);
    int m;
    scanf("%d",&m);
    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,cmp1);

    for(int i = 1; i <= m; i++){
        while(l < q[i].l)del(l++);//l右移
        while(l > q[i].l)add(--l);//l左移
        while(r < q[i].r)add(++r);//r右移
        while(r > q[i].r)del(r--);//r左移
        ans[q[i].id] = now;
    }
    for(int i = 1; i <= m; i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}

统计\(\sum_{i=1}^{k}c_i^2\)
传送门

其中\(c_i\)表示数字i在\([l,r]\)中出现的次数,k是最大值

cnt[i]记录i出现的次数,那么

如果cnt[a[i]]++, \((x + 1)^2 = x^2+2x + 1\)也就是now = now + 2 * cnt[a[i]] + 1

如果cnt[a[i]]--,\((x - 1 )^2 =x ^2 -2x + 1\)也就是now = now - 2 *cnt[a[i]] + 1

因为是统计出现次数,所以不需要判断是否出现过

void add(int x){
    now += (cnt[a[x]] << 1) + 1;
    cnt[a[x]]++;
}
void del(int x){
    now -= (cnt[a[x]] << 1) - 1;
    cnt[a[x]]--;
}

普通莫队

标签:print   根据   最大   ret   --   基本操作   统计   ios   class   

原文地址:https://www.cnblogs.com/Emcikem/p/12341173.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!