[分块] BZOJ 2724 [Violet 6]蒲公英

本文介绍了一种通过分块预处理来优化众数查询效率的算法。该算法将输入序列划分成若干个块,并利用特定定理预先计算每两块间的众数,从而将查询时间复杂度降低到O(1)。适用于需要频繁查询众数的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先填个坑 有时间再去看CLJ的题解
CLJ题解里有这样的一个定理

mode(S) 为可重集合 S 的众数
mode(AB)mode(A)B
证明 如果 tmode(A)B 那么 t mode(AB)中的出现次数就是在 A 中出现的次数 不会多于mode(A)

把序列分成 n
那么我们就可以利用这个定理预处理出 任意两块之间的众数
询问时就是比较连续的整块的众数和剩余的数出现次数
如果直接用二分来查找一个数 x 在区间[l,r]中出现的次数 复杂度 O(nnlogn)
如果用分块预处理 花费空间换时间 可以做到 O(1) 询问出现次数 那么就是 O(nn)
人懒没办法

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}

inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=40005;

int sx[N],icnt;
inline int Bin(int x){
  return lower_bound(sx+1,sx+icnt+1,x)-sx;
}
vector<int> ps[N];
inline int Find(int i,int l,int r){
  return upper_bound(ps[i].begin(),ps[i].end(),r)-lower_bound(ps[i].begin(),ps[i].end(),l);
}
#define Pair(x,l,r) (abcd(Find(x,l,r),x))

struct abcd{
  int first,second;
  abcd(int x=0,int y=0):first(x),second(y) { }
  bool operator < (const abcd &B) const{
    return first==B.first?second>B.second:first<B.first;
  }
};

const int B=205;

int n,a[N];
int Blo;
int pos[N];
int lp[B],rp[B],tot;
abcd mode[B][B];

inline int Query(int l,int r){
  int lb=pos[l],rb=pos[r];
  abcd ret=abcd(0,0);
  if (lb==rb){
    for (int i=l;i<=r;i++)
      ret=max(ret,Pair(a[i],l,r));
    return sx[ret.second];
  }
  if (lb+1<=rb-1)
    ret=Pair(mode[lb+1][rb-1].second,l,r);
  for (int i=l;i<=rp[lb];i++)
    ret=max(ret,Pair(a[i],l,r));
  for (int i=lp[rb];i<=r;i++)
    ret=max(ret,Pair(a[i],l,r));
  return sx[ret.second];
}

int main(){
  int Q,lastans=0,l,r;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(n); read(Q);
  for (int i=1;i<=n;i++) read(a[i]),sx[++icnt]=a[i];
  sort(sx+1,sx+icnt+1); icnt=unique(sx+1,sx+icnt+1)-sx-1;
  for (int i=1;i<=n;i++)
    a[i]=Bin(a[i]),ps[a[i]].push_back(i);
  Blo=sqrt(n);
  for (int i=1;i<=n;i++) pos[i]=(i-1)/Blo+1; tot=pos[n];
  for (int i=1;i<=tot;i++) lp[i]=Blo*(i-1)+1,rp[i]=Blo*i; rp[tot]=n;
  for (int i=1;i<=tot;i++)
    for (int j=i;j<=tot;j++){
      mode[i][j]=abcd(0,0);
      if (i<j)
    mode[i][j]=Pair(mode[i][j-1].second,lp[i],rp[j]);
      for (int k=lp[j];k<=rp[j];k++)
    mode[i][j]=max(mode[i][j],Pair(a[k],lp[i],rp[j]));
    }
  while (Q--){
    read(l); read(r);
    l=(l+lastans-1)%n+1; r=(r+lastans-1)%n+1;
    if (l>r) swap(l,r);
    printf("%d\n",lastans=Query(l,r));
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值