BZOJ2724 [Violet 6]蒲公英 解题报告【数据结构】【分块】

Description

Input

修正一下
l = (l_0 + x - 1) mod n + 1, r = (r_0 + x - 1) mod n + 1
Output

Sample Input
6 3
1 2 3 2 1 2
1 5
3 6
1 5
Sample Output
1
2
1
HINT

修正下:
n <= 40000, m <= 50000
解题报告
传说中这是一道分块的经典题。
我们不难看出,这道题要我们强制在线,加上只是查找众数没有更改,不方便合并区间,让人不由自主地想到了分块。那么这个题用分块怎么做呢?
我们又均值不等式得,分成sqrt(n)块时时间复杂度最低。我们先预处理出几个值:

-f[i][j] 存储这样一个pair:(第i到第j块分块出现的次数最多的数的次数,这个数的值的负值)。由于我们知道比较pair的最大值是先比first再比second,这样一来就可以保证这个数组是最优的。

-s[i][j] 代表前i块中j这个数出现的次数的和(前缀和)。

由于这个题不可能直接开一个筒来存储每一个数出现的次数,我们的方法是先将原数组排序,离散化,再将进行这些操作之前的这个数组每个元素的排名存储下来。往后我们存储次数的时候就直接存储排名,而非元素本身。
要点似乎就是这些,代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
const int N=4e4;
const int M=350;
int n,m,blk,cnt;
int lasans;
int a[N+5],b[N+5],c[N+5],bl[N+5],L[M+5],R[M+5],s[M+5][N+5];
pair<int,int>f[M+5][M+5];
int temp[N+5];
int query(int lf,int rg)
{
    pair<int,int>ret=f[bl[lf]+1][bl[rg]-1];
    if(bl[lf]==bl[rg])//在同一块里边,暴力 
    {
        for(int i=lf;i<=rg;i++)temp[b[i]]=0;
        for(int i=lf;i<=rg;i++)ret=max(ret,make_pair(++temp[b[i]],-b[i]));
    }
    else//不再同一块里,答案要么就是f[bl[lf]+1][bl[rg]-1],要么就是不再上述块中的数在[lf,rg]中出现的次数 
    {
        for(int i=lf;i<=R[bl[lf]];i++)temp[b[i]]=s[bl[rg]-1][b[i]]-s[bl[lf]][b[i]];//左边的零头中的元素在整块内的个数 
        for(int i=L[bl[rg]];i<=rg;i++)temp[b[i]]=s[bl[rg]-1][b[i]]-s[bl[lf]][b[i]];//右边的零头中的元素在整块内的个数 
        for(int i=lf;i<=R[bl[lf]];i++)ret=max(ret,make_pair(++temp[b[i]],-b[i]));//左边的零头中的元素在查询区间内的个数
        for(int i=L[bl[rg]];i<=rg;i++)ret=max(ret,make_pair(++temp[b[i]],-b[i]));//右边的零头中的元素在查询区间内的个数
    }
    return -ret.second;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]),b[i]=a[i];
    sort(a+1,a+1+n);
    a[0]=unique(a+1,a+1+n)-a-1;//离散化 
    for(int i=1;i<=n;i++)
    b[i]=lower_bound(a+1,a+1+a[0],b[i])-a;//在b数组中存储排名 
    blk=sqrt(n);
    if(n%blk)cnt=n/blk+1;
    else cnt=n/blk;
    for(int i=1;i<=n;i++)bl[i]=(i-1)/blk+1;
    for(int i=1;i<=cnt;i++)L[i]=(i-1)*blk+1,R[i]=i*blk;
    R[cnt]=n;
    for(int i=1;i<=cnt;i++)
    {
        memset(c,0,sizeof(c));
        pair<int,int>now(0,0);
        for(int j=L[i];j<=n;j++)
        {
            c[b[j]]++;
            now=max(now,make_pair(c[b[j]],-b[j]));//计算出第i块到第j块的答案和他的值 
            f[i][bl[j]]=now;
        }
        for(int j=1;j<=a[0];j++)s[i][j]=s[i-1][j];
        for(int j=L[i];j<=R[i];j++)s[i][b[j]]++;//前缀和 
    }
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        int lf=(l+lasans-1)%n+1,rg=(r+lasans-1)%n+1;
        if(lf>rg)swap(lf,rg);
        lasans=a[query(lf,rg)];//返回的只是排名 
        printf("%d\n",lasans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值