BZOJ 2724蒲公英 (分块) 【内有块大小证明】

题面

luogu传送门

T2des(6).gif

T2input(6).gif

T2output(5).gif

T2hint(5).gif

分析

先分块,设块大小为x(之后我们会证明块大小取何值会更优)

步骤1

把所有的数离散化,然后对每个值开一个vector pos[i],pos[i]存储数i出现的位置

我们设查询的区间为[l,r],需要求数v出现的次数,然后在vector中二分查找出第一个>=l的数的位置p1,和第一个>r的数的位置p2,p2-p1即为数v出现的次数

例:
离散化后的数组a={1,3,3,2,3,1,3 },则pos[3]={2,3,5,7},因为第2,3,5个数为3

我们需要查找[2,6]中数3出现的次数,发现p1=2,p2=4,出现的次数即为2
步骤2

然后我们考虑询问,如果只记录每个块里的众数显然是不行的,因为我们需要把许多个块的结果合起来,而众数不满足区间可加性,无法在\(O(\sqrt n)\)的时间内完成结果合并

因此我们预处理出所有块端点组成的区间,\(mode[l][r],maxt[l][r]\),表示[第l个块的起点,第r个块的终点]这个区间里的众数和众数的出现次数

查询[l,r]时我们可以得到中间的一个由块端点组成的区间,可以直接通过刚刚的预处理得到众数和众数的出现次数

两边多余的部分直接用步骤1暴力即可

至于mode,maxt数组如何预处理,直接用两重for循环来实现

首先枚举块的起点i,然后j从i遍历到n,用一个临时数组cnt来记录每个数出现的次数,就可以求出区间[i,j]的众数,如果j正好是块端点,则记录答案

for(int i=1; i<=bl; i++) {
        int ans=INF;
        int tim=0;
        for(int j=lb(i); j<=n; j++) {
            cnt[a[j]]++;
            if(cnt[a[j]]>tim||(cnt[a[j]]==tim&&a[j]<ans)) {
                ans=a[j];
                tim=cnt[a[j]];
            }
            if(j%sz==0) {
                mode[i][j/sz]=ans;
                maxt[i][j/sz]=tim;
            }else if(j==n){
                mode[i][bl]=ans;
                maxt[i][bl]=tim;
            }
        }
        for(int j=lb(i); j<=n; j++) cnt[a[j]]=0;
    }
时间复杂度分析

设块的大小为x,假设n为x的倍数

初始化部分:

从第一个块末尾需要遍历n-x次,从第二个块末尾需要遍历n-2x次,从第\(\frac{n}{x}\)个块末尾需要遍历\(n-x·\frac{n}{x}\)

总的遍历次数为

\[(n-x)+(n-2x)+\dots+n-x·\frac{n}{x}=\frac{n^2}{2x}-\frac{1}{2}n\]

其中\(\frac{1}{2}n\)可忽略,时间复杂度为\(O(\frac{n^2}{x})\)

查询部分:

考虑查询的极端情况,查询[2,n-1]

则需要遍历左,右长度各为(x-1)的块(可近似看成x)

时间复杂度为\(O(x \log n)\)

m次询问\(O(mx \log n)\)

所以总时间复杂度为\(O(\frac{n^2}{x}+mx \log n)\)

根据均值不等式

\(x=\sqrt{\frac{n^2}{m \log n}}\)时,总时间复杂度为\(2\sqrt{n^2m\log n}=O(n\sqrt{m\log n})\)

因此块大小为\(\sqrt{\frac{n^2}{m \log n}}\)时最优,由于n,m同级,可近似取\(\sqrt{n \log n}\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<cmath>
#include<vector>
#define maxn 100005
#define maxs 2005
#define INF 0x7fffffff 
using namespace std;
inline void qread(int &x) {
    x=0;
    int sign=1;
    char c=getchar();
    while(c<'0'||c>'9') {
        if(c=='-') sign=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9') {
        x=x*10+c-'0';
        c=getchar();
    }
    x=x*sign;
}
inline void qread(long long &x) {
    x=0;
    long long sign=1;
    char c=getchar();
    while(c<'0'||c>'9') {
        if(c=='-') sign=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9') {
        x=x*10+c-'0';
        c=getchar();
    }
    x=x*sign;
}
inline void qprint(int x) {
    if(x<0) {
        putchar('-');
        qprint(-x);
    } else if(x==0) {
        putchar('0');
        return;
    } else {
        if(x/10>0) qprint(x/10);
        putchar('0'+x%10);
    }
}

int n,m,num,sz,bl;
int id[maxn];//第i个位置属于的块编号
int a[maxn];
int b[maxn];
inline int lb(int id) {//求第id个块的左端点
    return sz*(id-1)+1;
}
inline int rb(int id) {//求第id个块的右端点
    return sz*id>n?n:sz*id;
}

vector<int>pos[maxn];
int get_count(int val,int l,int r) {
    return upper_bound(pos[val].begin(),pos[val].end(),r)-lower_bound(pos[val].begin(),pos[val].end(),l);
}

int cnt[maxn];
int mode[maxs][maxs];
int maxt[maxs][maxs];
void ini() {
    for(int i=1; i<=n; i++) {
        pos[a[i]].push_back(i);
    }
//  for(int i=1; i<=num; i++) {
//      sort(pos[b[i]].begin(),pos[b[i]].end());
//  }
    for(int i=1; i<=bl; i++) {
        int ans=INF;
        int tim=0;
        for(int j=lb(i); j<=n; j++) {
            cnt[a[j]]++;
            if(cnt[a[j]]>tim||(cnt[a[j]]==tim&&a[j]<ans)) {
                ans=a[j];
                tim=cnt[a[j]];
            }
            if(j%sz==0) {
                mode[i][j/sz]=ans;
                maxt[i][j/sz]=tim;
            }else if(j==n){
                mode[i][bl]=ans;
                maxt[i][bl]=tim;
            }
        }
        for(int j=lb(i); j<=n; j++) cnt[a[j]]=0;
    }
}

int query(int l,int r) {
    int ans=INF;
    int tim=0;
    if(id[l]+1<=id[r]-1) {
        ans=mode[id[l]+1][id[r]-1];
        tim=maxt[id[l]+1][id[r]-1];
    }
    for(int i=l; i<=min(r,rb(id[l])); i++) {
        int tmp=get_count(a[i],l,r);
        if(tmp>tim||(tmp==tim&&a[i]<ans)) {
            ans=a[i];
            tim=tmp;
        }
    }
    if(id[l]!=id[r]) {
        for(int i=lb(id[r]); i<=r; i++) {
            int tmp=get_count(a[i],l,r);
            if(tmp>tim||(tmp==tim&&a[i]<ans)) {
                ans=a[i];
                tim=tmp;
            }
        }
    }
//  return ans;
    return b[ans];
}

int main() {
    int l,r;
    qread(n);
    qread(m);
    for(int i=1; i<=n; i++) {
        qread(a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    num=unique(b+1,b+1+n)-b-1;
    for(int i=1; i<=n; i++) {
        a[i]=lower_bound(b+1,b+1+num,a[i])-b;
    }
    sz=sqrt(n/(log(n)/log(2)));
    bl=1;
    for(int i=1; i<=n; i++) {
        id[i]=bl;
        if(i%sz==0) bl++;
    }
    int x=0;
    ini();
    for(int i=1; i<=m; i++) {
        qread(l);
        qread(r);
        l=(l+x-1)%n+1;
        r=(r+x-1)%n+1;
        if(l>r) swap(l,r);
        x=query(l,r);
        qprint(x);
        putchar('\n');
    }
}

转载于:https://www.cnblogs.com/birchtree/p/10413710.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值