日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

分块简单入门

發布時間:2025/3/14 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分块简单入门 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

分塊簡單入門

樹狀數組雖超級快,但是很僵硬,不靈活;線段樹雖快,但是卻不直觀,代碼量大;所以,速度較慢但直觀形象、代碼量小的分塊大法不實為線段樹的替代品。

網絡上關于分塊的教程不知道為什么很少,雖然早有hzwer大神的分塊九講,但是還是少了入門級詳解教程。此篇將分為三個階段,保證初學者在有意識地閱讀后基本掌握分塊。

1.簡單的入門題

問題引入

給定長度為N(N<=1e5)的正數數列A,然后輸入Q(Q<=1e5)行操作命令,指令形如Q l r,表示統計數列中第l~r個數中的最大值

思路

首先想到的簡單暴力遍歷在這個數量級是會超時,這道題我們可以用分塊來做。
先將長度為n,從1開始標號的數組a分為 sqrt(n)塊(此時時間復雜度最小)維護,那么易得以下推論

  • a[i]處于(i-1)/sqrt(n)+1塊
  • 第i塊的左端點為(i-1)*sqrt(n)+1,右端點為min(i*sqrt(n), n)

以上推論便于理解后續實現,可以自己動手模擬驗證一下

我們可以先預處理出每一塊的最大值并存入數組m[]中,于是對于命令Q l r,我們可以將查詢區間[l,r]分為兩個部分——所有的整塊(整區間)部分和開頭和結尾不足一整區間的部分。然后對于整區間我們直接取已維護好了的區間最大值m[i]進行比較,而對于開頭和結尾不足一整區間的部分,我們樸素暴力遍歷,最終合并答案,得解。

這便是分塊算法最基本的思路,概括起來就是大段維護,局部樸素

實現

說明:
blo為區間大小,pos[i]表示a[i]元素位于第pos[i]塊,其他變量名同上

例碼:

#include <iostream> #include <cmath> #define MAXN 100001 #define MAX(a,b) ((a)>(b)?(a):(b))//宏 #define MIN(a,b) ((a)<(b)?(a):(b)) using namespace std; int blo,n,q,a[MAXN],m[MAXN],pos[MAXN]; int query(int l, int r){int ans=0;for(register int i=l;i<=MIN(r, pos[l]*blo);i++)//統計左區間(或者查詢區間)ans=MAX(ans, a[i]);for(register int i=pos[l]+1;i<=pos[r]-1;i++)//統計整塊區間ans=MAX(ans, m[i]);if(pos[l]!=pos[r])//如果存在右區間才遍歷,防止重復計算for(register int i=(pos[r]-1)*blo+1;i<=r;i++)//統計右區間ans=MAX(ans, a[i]);return ans; } int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);//輸入輸出加速cin>>n;blo=sqrt(n);for(int i=1;i<=n;i++) cin>>a[i],pos[i]=(i-1)/blo+1,m[pos[i]]=MAX(m[pos[i]], a[i]);//初始化cin>>q;char type;int l,r;while(q--){cin>>type>>l>>r;cout<<query(l,r)<<endl;}return 0; }

2.較難的區間修改

問題引入

給定長度為N(N<=1e5)的數列A,然后輸入Q(Q<=1e5)行操作命令

  • 第一類指令形如C l r d,表示把數列中第l~r個數都加d
  • 第二類指令形如Q l r,表示統計數列中第l~r個數的和

思路

同樣如上,將長度為n,從1開始標號的數組a分為 sqrt(n)塊維護

然后對于操作命令C l r d,[l,r]開頭和結尾不足一整區間的部分暴力操作并維護區間之和sum[];而[l,r]包含的所有整區間則不用暴力操作,而是使用一個add[]數組(類似于線段樹的lazy[]延遲標記)記錄操作值。

對于命令Q l r也就同理,不足一整區間的部分暴力相加并再加上add[]標記乘以當前區間的個數(因為為了分塊算法的效率,操作整塊區間時并沒有真的修改區間內元素所有值,而是直接用延遲標記add[]暫時記錄下來,所以當后面再單獨操作取值時,就必須下放延遲標記);而整塊部分則直接sum[]相加即可

總體時間復雜度為
\[ O((N+Q)*\sqrt[]{N}) \]

實現

例碼:

#include <iostream> #include <algorithm> #include <cmath> #define MAXN 100010 using namespace std; int n,q,a[MAXN],blo,sum[MAXN],add[MAXN],pos[MAXN]; void change(int l, int r, int d){for(register int i=l;i<=min(pos[l]*blo,r);i++)a[i]+=d,sum[pos[l]]+=d;for(register int i=pos[l]+1;i<=pos[r]-1;i++)add[i]+=d;if(pos[l]!=pos[r])//防止重復計算for(register int i=(pos[r]-1)*blo+1;i<=r;i++)a[i]+=d,sum[pos[r]]+=d; } int query(int l, int r){int res=0;for(register int i=l;i<=min(pos[l]*blo,r);i++)res+=a[i]+add[pos[l]];for(register int i=pos[l]+1;i<=pos[r]-1;i++)res+=sum[i]+add[i]*blo;if(pos[l]!=pos[r])//防止重復計算for(register int i=(pos[r]-1)*blo+1;i<=r;i++)res+=a[i]+add[pos[r]];return res; } int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);//輸入輸出加速cin>>n;blo=(int)sqrt(n);for(int i=1;i<=n;i++)cin>>a[i],pos[i]=(i-1)/blo+1,sum[pos[i]]+=a[i];//初始化cin>>q;char type;int l,r,d;for(int i=0;i<q;i++){cin>>type>>l>>r;if(type=='C'){cin>>d;change(l,r,d);}elsecout<<query(l,r)<<endl;}return 0; }

擴展

其實本題的操作還可以包含區間乘法,即操作涉及區間加法、區間乘法,n個區間和詢問。具體思路就是開兩個標記數組,分別維護區間加法和區間乘法。值得注意的是,每一次下放標記時,乘法的運算優先級要高于加法,比如當前塊加法標記已為m,那么再進行一個乘n的操作后,加法標記應更新為n*m

3.難題

問題引入

[Violet]蒲公英 洛谷 P4168

思路

在線分塊大法,首先離散化一下,然后預處理出m[a][b]數組,表示第a塊到第b塊中蒲公英出現次數最多的編號為m[a][b];在查詢區間[l,r]時,采用分塊思想,整區間直接取出與首尾暴力得到的答案進行合并,得解。

實現

(在洛谷上沒開O2就RE,開了O2就AC了,也不知道為什么(;′д`)ゞ)

// luogu-judger-enable-o2 #include <iostream> #include <cmath> #include <cstring> #include <algorithm> #include <vector> #include <map> #define MAXN 50004 #define MIN(a,b) ((a)<(b)?(a):(b)) using namespace std; int n,q,a[MAXN],num[MAXN],m[505][505][2],blo,cnt[MAXN]; int t[MAXN]; vector <int> po[MAXN]; int getb(int x){//獲取a[x]所在塊編號return (x-1)/blo+1; } int getlp(int b){//獲取第i塊的左端點return (b-1)*blo+1; } int getrp(int b){//獲取第i塊的右端點return MIN(b*blo, n); } int getnum(int l, int r, int x){//獲取區間[l,r]中x出現的次數return upper_bound(po[x].begin(),po[x].end(),r)-lower_bound(po[x].begin(),po[x].end(),l); } void load(int x){//預處理memset(cnt, 0, sizeof(cnt));int mx=0, mx_num=0;for(int i=x;i<=getb(n);i++){for(int j=getlp(i);j<=getrp(i);j++){cnt[a[j]]++;if(cnt[a[j]]>mx||(cnt[a[j]]==mx&&a[j]<mx_num)){//注意如果有若干種蒲公英出現次數相同,則輸出種類編號最小的那個mx=cnt[a[j]];mx_num=a[j];}}m[x][i][0]=mx_num;m[x][i][1]=mx;} } int query(int l, int r){//查詢int mx_num=m[getb(l)+1][getb(r)-1][0];int mx=m[getb(l)+1][getb(r)-1][1];for(register int i=l;i<=MIN(r, getrp(getb(l)));i++){int t=getnum(l, r, a[i]);if(t>mx||(t==mx&&a[i]<mx_num)){mx=t;mx_num=a[i];}}if(getb(l)!=getb(r))for(register int i=getlp(getb(r));i<=r;i++){int t=getnum(l, r, a[i]);if(t>mx||(t==mx&&a[i]<mx_num)){mx=t;mx_num=a[i];}}return num[mx_num]; } int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>q;blo=sqrt(n);for(int i=1;i<=n;i++){cin>>a[i],t[i]=a[i];}sort(t+1, t+1+n);int len=unique(t+1, t+1+n)-(t+1);for(int i=1;i<=n;i++){int temp=lower_bound(t+1, t+1+len, a[i])-t;//從1開始num[temp]=a[i],a[i]=temp;}for(int i=1;i<=n;i++)po[a[i]].push_back(i);for(int i=1;i<=getb(n);i++) load(i);int x=0;while(q--){int l,r;cin>>l>>r;l=(l+x-1)%n+1,r=(r+x-1)%n+1;if(l>r) swap(l,r);x=query(l, r);cout<<x<<endl;}return 0; }

參考

  • 《算法競賽進階指南》
  • 數列分塊入門1 – 9 by hzwer

本文采用?知識共享 署名-非商業性使用-相同方式共享 3.0 中國大陸 許可協議進行許可。歡迎轉載,請注明出處: 轉載自:Santiego的博客

轉載于:https://www.cnblogs.com/santiego/p/9452424.html

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的分块简单入门的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。