《演算法競賽進階指南》0x44 分塊 線上求區間眾數
阿新 • • 發佈:2020-07-21
題目連結:https://www.acwing.com/problem/content/description/251/
題目中下一次的詢問一定要在上一次的答案的基礎上計算,所以屬於線上回答的題目,此時只能用分塊進行求解,有兩種分塊的策略。
1.維護一個數組,記錄在塊斷開的兩個位置中的每個數出現的次數,然後求[l,r]的眾數數量的時候只需要在完整分塊的基礎上進行疊加順便更新答案即可,這裡計算的時候可以設定一個x,y來代表完整區間的開始位置和結束位置,如果沒有一箇中間的完整區間,x,y就是0,屬於一點技巧性的東西。
2.區間內的眾數要麼是完整區間中的眾數,要麼是不完整片段中沒一個出現的數,所以只需要對這些數在其出現的位置構成的vector中二分搜尋在[l,r]區間之內的數量即可,然後更新答案。
方法一的程式碼:
#include<iostream> #include<cstdio> #include<algorithm> #include<string.h> #include<math.h> using namespace std; const int T = 37; const int maxn = 40010; int a[maxn],b[maxn],L[T],R[T],c[T][T][maxn],f[T][T][2],now[2]; int pos[maxn]; //0:眾數的數量,1:眾數 inline void work(intx,int y ,int num){//加數並且統計眾數 ++c[x][y][num]; //取數量大的,數量一樣取較小的 if(c[x][y][num]>now[0] || (c[x][y][num]==now[0] && now[1]>num)){ now[0]=c[x][y][num]; now[1]=num; } return ; } int query(int l,int r){ int p=pos[l],q=pos[r]; int x=0,y=0; if(p+1<=q-1){ x=p+1; y=q-1; } memcpy(now,f[x][y],sizeof(now));//將眾數資訊初始化 if(p==q){//此時是在0,0上進行操作,所有數的統計次數是0 for(int i=l;i<=r;i++)work(x,y,a[i]); for(int i=l;i<=r;i++)--c[x][y][a[i]]; }else{ for(int i=l;i<=R[p];i++)work(x,y,a[i]); for(int i=L[q];i<=r;i++)work(x,y,a[i]); for(int i=l;i<=R[p];i++)--c[x][y][a[i]];//還原狀態 for(int i=L[q];i<=r;i++)--c[x][y][a[i]]; } return b[now[1]];//now[1]中儲存的是索引 } int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++)scanf("%d",&a[i]); memcpy(b,a,sizeof(a)); sort(b+1,b+n+1);//在b中離散化,將索引儲存在a中 int tot = unique(b+1,b+n+1)-(b+1); for(int i=1;i<=n;i++){ a[i]=lower_bound(b+1,b+tot+1,a[i])-b; } int t=pow((double)n,(double)1/3); int len=t?n/t:n;//n^(1/3)為0的話長度就是n,否則可以劃分分塊 for(int i=1;i<=t;i++){ L[i]=(i-1)*len+1; R[i]=i*len; } if(R[t]<n){ ++t; L[t]=R[t-1]+1; R[t]=n; } for(int i=1;i<=t;i++) for(int j=L[i];j<=R[i];j++){ pos[j]=i; } memset(f,0,sizeof(f)); memset(c,0,sizeof(c)); for(int i=1;i<=t;i++)//處理出每個[L,R]處的眾數 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<=tot;k++){ if(c[i][j][k]>f[i][j][0]){//儲存眾數 f[i][j][0]=c[i][j][k]; f[i][j][1]=k; } } } int x=0; while(m--){ int l,r; scanf("%d%d",&l,&r); l=(l+x-1)%n+1; r=(r+x-1)%n+1; if(l>r)swap(l,r); x=query(l,r); printf("%d\n",x); } return 0; }
方法二的程式碼:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; const int maxn = 40010; const int T = 900;//最多被分成大約785個分塊 int c[maxn],a[maxn],b[maxn],f[T][T]; int pos[maxn],L[T],R[T]; vector<int> v[maxn]; int get(int x,int l,int r){ return upper_bound(v[x].begin(),v[x].end(),r) -lower_bound(v[x].begin(),v[x].end(),l); } void find(int x,int l,int r,int& ans,int& cnt){ int num=get(x,l,r); if(num>cnt || (num==cnt && x<ans)){ ans=x; cnt=num; } } int query(int l,int r){ int p=pos[l],q=pos[r]; int ans=0,cnt=0; if(p==q){ for(int i=l;i<=r;i++) find(a[i],l,r,ans,cnt); return b[ans]; } int x=0,y=0; if(p+1<=q-1){ x=p+1; y=q-1; } for(int i=l;i<=R[p];i++)find(a[i],l,r,ans,cnt); for(int i=L[q];i<=r;i++)find(a[i],l,r,ans,cnt); if(f[x][y])//中間存在完整片段 find(f[x][y],l,r,ans,cnt); return b[ans]; } int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++)scanf("%d",&a[i]); memcpy(b,a,sizeof(b)); sort(b+1,b+n+1); int tot=unique(b+1,b+n+1)-(b+1); for(int i=1;i<=n;i++){ a[i]=lower_bound(b+1,b+tot+1,a[i])-b; v[a[i]].push_back(i); } int t=sqrt(log(n)/log(2)*n); int len=t?n/t:n; for(int i=1;i<=t;i++){ L[i]=(i-1)*len+1; R[i]=i*len; } if(R[t]<n){ ++t; L[t]=R[t-1]+1; R[t]=n; } for(int i=1;i<=t;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i; memset(f,0,sizeof(f)); for(int i=1;i<=t;i++){//對每個分塊都掃描整個長度,得出以該左端點為起點的序列的眾數 memset(c,0,sizeof(c)); int ans=0,cnt=0; for(int j=L[i];j<=n;j++){ ++c[a[j]]; if(c[a[j]]>cnt || (c[a[j]]==cnt && a[j]<ans)){ ans=a[j]; cnt=c[a[j]]; } f[i][pos[j]]=ans; } } int x=0; while(m--){ int l,r; scanf("%d%d",&l,&r); l=(l+x-1)%n+1; r=(r+x-1)%n+1; if(l>r)swap(l,r); x=query(l,r); printf("%d\n",x); } return 0; }