P4168 [Violet]蒲公英
作者:互联网
题意描述
题面好美好啊
强制在线查询区间众数。
算法分析
分块模板题了吧。
类似区间众数等不满足区间加法的问题很难用线段树或树状数组来实现,所以这里采用分块。
其实分块几乎都是趋近于大块维护,小块暴力的思想,所以代码实现难度和思维难度没有上面提到的数据结构高。
思路是将 \(N\) 个数分为 \(T\) 个区间,至于 \(T\) 的取值之后再讨论。
首先进行离散化是肯定的。
对于每个区间维护 \(c(i,j,k)\) 表示:区间 \(i\) 到区间 \(j\) 之间有多少个 \(k\)。
同时维护 \(f(i,j)\) 和 \(d(i,j)\) 分别表示区间 \(i\) 与区间 \(j\) 之间的众数的数量和数字。
那么每次查询区间 \([x,y]\) 时,只需要对于两边的小块进行处理即可。
时间复杂度为 \(O(NT^2+MN/T)\),显然当 \(NT^2=MN/T\) 时总时间复杂度最低。
假设 \(N,M\) 为同一数量级,那么当 \(T=\sqrt[3]{N}\) 时总时间复杂度最小,此时为 \(O(N^{5/3})\) 量级。
考虑一下优化,发现时间卡在 \(O(NT^2)\) 的预处理上,其实并不需要这么高复杂度的预处理。
我们可以维护 \(sum(i,j)\) 表示前 \(i\) 个区间中数字 \(j\) 的个数。
此时时间复杂度变为 \(O(T^3+MN/T)\),此时 \(T\) 取 \(\sqrt N\) 可以实现 \(O(N\sqrt N)\) 的时间复杂度。
至此实现了分块能达到的最快时间复杂度了吧。(dalao 有更多优化可以私信蒟蒻)
代码实现
首先是 \(O(N^{5/3})\) 的分块:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define N 40010
#define T 40
using namespace std;
int n,q,t;
int nowl,nowr,num,mx;
int a[N],fa[N],L[T],R[T];
int c[T][T][N],f[T][T],d[T][T];
int read(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
return x*f;
}
void pre_work(){
//分块
t=(int)pow(n*1.0,1.0/3);
int l;
if(t) l=n/t;
for(int i=1;i<=t;i++) L[i]=(i-1)*l+1,R[i]=i*l;
if(R[t]<n) L[t+1]=R[t]+1,R[++t]=n;
//离散化
sort(fa+1,fa+n+1);
int m=unique(fa+1,fa+n+1)-(fa+1);
for(int i=1;i<=n;i++) a[i]=lower_bound(fa+1,fa+m+1,a[i])-fa;
//预处理
for(int i=1;i<=t;i++)
for(int j=i;j<=t;j++){
for(int k=L[i];k<=R[j];k++) c[i][j][a[k]]++;
for(int k=1;k<=m;k++)
if(c[i][j][k]>f[i][j] || c[i][j][k]==f[i][j] && k<d[i][j])
f[i][j]=c[i][j][k],d[i][j]=k;
}
return;
}
void update(int x){
c[nowl][nowr][a[x]]++;
if(c[nowl][nowr][a[x]]>mx ||c[nowl][nowr][a[x]]==mx && a[x]<num)
num=a[x],mx=c[nowl][nowr][a[x]];
return;
}
int solve(int x,int y){
int l,r;
if(x>y) swap(x,y);
for(int i=1;i<=t;i++) if(x<=R[i]) {l=i;break;}
for(int i=t;i>=1;i--) if(y>=L[i]) {r=i;break;}
if(l+1<=r-1) nowl=l+1,nowr=r-1; else nowl=nowr=0;
//如果两者之间的距离不足一个区间的长度就直接暴力。
num=d[nowl][nowr];mx=f[nowl][nowr];
if(l==r){
for(int i=x;i<=y;i++) update(i);
for(int i=x;i<=y;i++) c[nowl][nowr][a[i]]--;
}else{//维护两边的剩余区间即可。
for(int i=x;i<=R[l];i++) update(i);
for(int i=L[r];i<=y;i++) update(i);
for(int i=x;i<=R[l];i++) c[nowl][nowr][a[i]]--;
for(int i=L[r];i<=y;i++) c[nowl][nowr][a[i]]--;
}
return fa[num];
}
int main(){
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read(),fa[i]=a[i];
pre_work();
int ans=0;
for(int i=1;i<=q;i++){
int x=read(),y=read();
ans=solve((x+ans-1)%n+1,(y+ans-1)%n+1);
printf("%d\n",ans);
}
return 0;
}
然后是前缀和优化之后的 \(O(N\sqrt N)\)。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define N 40010
#define T 220
using namespace std;
int n,q,t;
int a[N],fa[N],b[N];
int L[T],R[T];
int sum[T][N];
struct node{
int num,cnt;
}p[T][T];
int read(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
return x*f;
}
void pre_work(){
//分块
t=sqrt(n);
int l;
if(t) l=n/t;
for(int i=1;i<=t;i++) L[i]=(i-1)*l+1,R[i]=i*l;
if(R[t]<n) L[t+1]=R[t]+1,R[++t]=n;
//离散化
sort(fa+1,fa+n+1);
int m=unique(fa+1,fa+n+1)-(fa+1);
for(int i=1;i<=n;i++) a[i]=lower_bound(fa+1,fa+m+1,a[i])-fa;
//预处理
for(int i=1;i<=t;i++){
memset(b,0,sizeof(b));node now;
now.cnt=now.num=0;
for(int j=i;j<=t;j++){
for(int k=(j-1)*l+1;k<=min(n,j*l);k++){
b[a[k]]++;
if(b[a[k]]>now.cnt || b[a[k]]==now.cnt && a[k]<now.num)
now.num=a[k],now.cnt=b[a[k]];
}
p[i][j]=now;
}
}
for(int i=1;i<=t;i++){
for(int j=1;j<=m;j++) sum[i][j]=sum[i-1][j];
for(int j=(i-1)*l+1;j<=min(n,i*l);j++) sum[i][a[j]]++;
}
return;
}
int num,mx,nowl,nowr;
void update(int x){
sum[nowr][a[x]]++;
//这里有一点特殊的改变,因为如果 nowl=0,那么 nowl-1=-1 导致数组越界。
int k=(!nowl)?sum[nowr][a[x]]:sum[nowr][a[x]]-sum[nowl-1][a[x]];
if(k>mx || k==mx && a[x]<num) mx=k,num=a[x];
}
int solve(int x,int y){
int l,r;
if(x>y) swap(x,y);
for(int i=1;i<=t;i++) if(x<=R[i]) {l=i;break;}
for(int i=t;i>=1;i--) if(y>=L[i]) {r=i;break;}
if(l+1<=r-1) nowl=l+1,nowr=r-1; else nowl=nowr=0;
num=p[nowl][nowr].num;mx=p[nowl][nowr].cnt;
if(l==r){
for(int i=x;i<=y;i++) update(i);
for(int i=x;i<=y;i++) sum[nowr][a[i]]--;
}else{
for(int i=x;i<=R[l];i++) update(i);
for(int i=L[r];i<=y;i++) update(i);
for(int i=x;i<=R[l];i++) sum[nowr][a[i]]--;
for(int i=L[r];i<=y;i++) sum[nowr][a[i]]--;
}
return fa[num];
}
int main(){
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read(),fa[i]=a[i];
pre_work();
int ans=0;
for(int i=1;i<=q;i++){
int x=read(),y=read();
ans=solve((x+ans-1)%n+1,(y+ans-1)%n+1);
printf("%d\n",ans);
}
return 0;
}
完结撒❀。
标签:分块,int,复杂度,&&,Violet,区间,include,蒲公英,P4168 来源: https://www.cnblogs.com/lpf-666/p/13539661.html