數列分塊入門9
首先上題目
題目描述
給出一個長為n的數列,以及n個操作,操作涉及詢問區間的最小眾數。
第一行輸入一個數字n。
第二行輸入n個數字,第i個數字為a[i],以空格隔開。
接下來輸入n行詢問,每行輸入兩個數字l,r以空格隔開。
表示查詢位於[l,r]的數字的眾數。
這道題遇到了之前沒有遇到過的眾數問題,那麼我們怎麼解決呢?
眾所周知,直接暴力可以騙分
暴力顯然是不行的,因此我們要考慮其他方法。
根據陳立傑的區間眾數解題報告可知,a和b集合的眾數,一定屬於a集合的眾數與b集合的交集,即一定在這兩個集合的某一個集合中出現(似乎是廢話)
解題思路:
如果邊界是[l,r],l在第a塊,r在第b塊,可以分成三個部分:
1.l到a最後一塊
2.a+1~b-1塊
3.第b塊到r
根據上面的性質,如果我們預先處理a+1~b-1塊的眾數,再去遍歷判斷第一部分和第三部分是否有更合適的眾數,這道題就能做出來了。
具體細節在下面程式碼中說:
1.建立分塊:
#include<bits/stdc++.h> using namespace std; /* 1.將陣列v[i]離散化,不記錄其值,只記錄其位數 2.分塊查詢 */ const int N=100005; int n,blo,id,bl[N],v[N],val[N],cnt[N]; int f[1005][1005]; vector<int> ve[N];//儲存編號相同,就是數相同時原來的第幾個值,用來二分求眾數 map<int,int> mp;
其中,bl儲存每個塊的邊界,val儲存原來的v陣列,cnt用來計算每個數出現的個數。
進入主函式
在主函式中,由於輸入進去的v陣列太大,我們並不需要那麼大的數
因此可以進行離散化,相同的數具有相同的標號,再用val來儲存原來的v陣列
當需要查詢其值大小的時候,只需要用val[v[i]]就行了
ve函式其實是查編號為v[i]的數在第幾位出現過,為後來的二分找下標做處理
int main() { cin>>n; blo=200;//每一塊分成200,在後期尋找中好計算 for(int i=1;i<=n;i++) { cin>>v[i]; if(!mp[v[i]])//之前沒有出現過 { mp[v[i]]=++id;//第幾個出現的數 val[id]=v[i];//記錄編號為i的原值 } v[i]=mp[v[i]];//記錄其是第幾個出現的數(離散化) ve[v[i]].push_back(i);//第v[i]個出現的數在哪裡出現,此句話為記錄個數所用, } for(int i=1;i<=n;i++) bl[i]=(i-1)/blo+1; for(int i=1;i<=bl[n];i++) pre(i);//將每一段進行眾數排序 for(int i=1;i<=n;i++) { int a,b; cin>>a>>b; if(a>b) swap(a,b); cout<<val[query(a,b)]<<endl;//輸出眾數 } return 0; }
在
for(int i=1;i<=bl[n];i++) pre(i);
這個語句是為了預處理在[i,n]中每個區間的眾數,接下來上程式碼:
預處理眾數
我們將整個處理眾數的過程看成是一個表
那麼我們需要算出來每一塊到每一塊區間的眾數,包括其本身
假如說有四個區間a,b,c,d;
那麼我們就需要算a-b,a-c,a-d,b-c,b-d,c-d,a,b,c,d這一共10個區間的眾數。
我們可以將這個看成是一個表格
那麼就可以開一個二維陣列f[1000][1000]用來記錄每一塊的眾數是什麼
void pre(int x)//預處理每一段的眾數
{
memset(cnt,0,sizeof(cnt));//一開始進行初始化
int mx=0,ans=0;
for(int i=(x-1)*blo+1;i<=n;i++)//第x個塊左邊界到n中的眾數
{
cnt[v[i]]++;//記錄每一段每個數出現的個數
int t=bl[i];//記錄這是第幾段
if(cnt[v[i]]>mx||(cnt[v[i]]==mx&&val[v[i]]<val[ans]))//找最小眾數
ans=v[i],mx=cnt[v[i]];
//x是初始段,t是末尾段
f[x][t]=ans;//這一段眾數是ans
}
}
至此,我們所需要的就已經準備好了。
查詢,解題
因為我們已經預先處理好每一塊的眾數,因此只需要判斷左右兩邊多出來的那一小部分中,有沒有數字可以當成是新的眾數。
其中第一個query函式是照應了在建函式中ve[v[i]].push_back(i)這一步,搜尋從l到r中標號為v[i]的數出現了多少次
int query(int l,int r,int x)//二分查詢這一塊內編號為x的數量(ve[v[i]].push_back(i);)
{
int t=upper_bound(ve[x].begin(),ve[x].end(),r)-lower_bound(ve[x].begin(),ve[x].end(),l);//大下標減去小下標
//注意前面是upper_bound,後面是lower_bound
return t;
}
int query(int a,int b)
{
int ans,mx;
ans=f[bl[a]+1][bl[b]-1];//意思是在(bl[a]+1~bl[b]-1)這一序列中的眾數
mx=query(a,b,ans);//查詢這個眾數在此區間裡出現的個數
for(int i=a;i<=min(bl[a]*blo,b);i++)
{
int t=query(a,b,v[i]);//暴力查詢左邊多餘部分每個數的數量
if(t>mx||(t==mx&&val[v[i]]<val[ans]))
ans=v[i],mx=t;
}
if(bl[a]!=bl[b])
for(int i=(bl[b]-1)*blo+1;i<=b;i++)//暴力查詢右邊多餘部分每個數的數量
{
int t=query(a,b,v[i]);
if(t>mx||(t==mx&&val[v[i]]<val[ans]))
ans=v[i],mx=t;
}
return ans;
}