更優體驗請移步CSDNios
\(NOIP\)已過,訓練難度瞬間變大。不少沒有學過的知識點以各類方式出如今題目裏。而本蒟蒻的腦子裏只有那慘兮兮一點點的算法,因而本蒟蒻就開始走上惡補知識點的道路。算法
忽然想起來好久好久以前有道用分塊作的題目,當時聽的雲裏霧裏,而後同年級的某位大佬表示:分塊很簡單。今天又聽到同窗提及分塊,就上OI-WIKI查了一下,沒想到很快就理解而後敲題了……數組
分塊實際上是一種思想,本質上跟暴力差很少,經常使用於處理區間問題。作法是將區間分紅一個個小塊,而後暴力維護,時間複雜度和分出來的塊的個數及塊的大小有關spa
結合例題講解分塊的具體操做.net
LOJ#6280.數列分塊入門4code
給出一個長爲\(n\)的數列,以及\(n\)個操做blog
操做有兩種狀況,每次操做輸入4個數\(opt、l、r、c\)get
若\(opt=0\),表示將位於\([l,r]\)的之間的數字都加\(c\)。io
若\(opt=1\),表示詢問位於\([l,r]\)的全部數字的和\(mod\ (c+1)\) 。入門
\(1\le n \le50000\),保證答案在\(long\ long\)範圍內
只要學過線段樹,第一秒想到的基本都會是線段樹,可是這題有一個很大的不一樣在於每次求值的時候會模一個非固定的數,那麼若是要用線段樹來作就須要在建樹的時候不取模,求值的時候再取模,這樣的話就有可能會致使線段樹上的值超過\(long\ long\)甚至\(unsigned\ long\ long\)範圍(\(\_\_int128\)就不要多想了)
因此咱們要拋棄線段樹,去尋找另外一種解決方案。而分塊,就是解決這個問題的一個很好的辦法
對於一個區間,咱們把它分紅若干個長度爲\(s\)的塊,最後一個塊的長度能夠不足\(s\),由於沒有規定\(s\)必須是\(n\)的因數
一個區間\(a\)就能夠分紅
\(\underbrace{a_1,a_2\ldots,a_s}_{b_1},\underbrace{a_{s+1},\ldots,a_{2s}}_{b_2},\dots,\underbrace{a_{(s-1)\times s+1},\dots,a_n}_{b_{\frac{n}{s}}}\)
其中\(b_i\)維護第\(i\)個塊內的和,能夠在讀入的時候就記錄好每一個元素是哪一個塊,同時維護\(b\)數組
分類討論一下
若是\(l,r\)在同一塊內,則直接暴力更改,時間複雜度\(O(s)\)
若是不在,就能夠分三部分:(1)以\(l\)開頭的一個不完整塊;(2)中間若干個完整塊;(3)以\(r\)結尾的一個不完整塊。其中(1)(3)部分能夠暴力更改,(2)部分就直接更改\(b_i\),以及\(x_i\)。其中\(x_i\)表示整個區間加上了多少,時間複雜度\(O(\dfrac{n}{s}+s )\)
跟更改很像,也是要分類討論
\(l,r\)在同一個塊內就直接暴力統計,同時注意加上\(x\)數組,時間複雜度\(O(s)\)
不在同一個塊內也是分三部分,分法同更改部分,(1)(3)部分查詢也是暴力,跟\(l,r\)同塊同樣,注意加上\(x\)數組。(2)部分直接加上中間完整塊的\(b\)數組,這裏就不用加上\(x\)數組。時間複雜度\(O(\dfrac{n}{s}+s)\)
綜合更改和查詢,一次操做的時間複雜度就是\(O(\dfrac{n}{s}+s)\),顯然當\(s\)是\(\sqrt{n}\)的時候是最優的,那麼一次操做的時間複雜度就是\(O(\sqrt{n})\),總時間複雜度\(O(n\sqrt{n})\)
#include<cstdio> #include<cmath> #define ll long long using namespace std; int n,s,opt,l,r,x; ll ans,a[50005],id[50005],b[50005],c[50005]; int read() { int res=0,fh=1;char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') fh=-1;ch=getchar();} while (ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar(); return res*fh; } int main() { n=read(); s=sqrt(n); for (int i=1;i<=n;++i) { a[i]=read(); id[i]=(i-1)/s+1; b[id[i]]+=a[i]; } for (int i=1;i<=n;++i) { opt=read();l=read();r=read();x=read(); if (!opt) { if (id[l]==id[r]) { for (int j=l;j<=r;++j) a[j]+=x,b[id[l]]+=x; } else { for (int j=l;id[j]==id[l];++j) a[j]+=x,b[id[l]]+=x; for (int j=id[l]+1;j<id[r];++j) c[j]+=x,b[j]+=s*x; for (int j=r;id[j]==id[r];--j) a[j]+=x,b[id[r]]+=x; } } else { ans=0; if (id[l]==id[r]) { for (int j=l;j<=r;++j) ans=(ans+a[j]+c[id[l]])%(x+1); } else { for (int j=l;id[j]==id[l];++j) ans=(ans+a[j]+c[id[l]])%(x+1); for (int j=id[l]+1;j<id[r];++j) ans=(ans+b[j])%(x+1); for (int j=r;id[r]==id[j];--j) ans=(ans+a[j]+c[id[r]])%(x+1); } printf("%lld\n",ans); } } return 0; }
在\(N(1\le N\le100000)\)個數\(A_1\dots A_n\)組成的序列上進行\(M(1\le M\le100000)\)次操做,操做有兩種:
(1)\(1\ L\ R\ C\):表示把\(A_L\)到\(A_R\)增長\(C\)\((|C|\le10000)\);
(2)\(2\ L\ R\):詢問\(A_L\)到\(A_R\)之間的最大值。
其實這是線段樹的模板題,放到這裏來是想要體現分塊在更改的時候的一個注意事項(實際上是爲了加字數)
首先仍是分塊的基本操做,每一個塊長度爲\(s\),同時維護每一個塊內的最大值\(b\)數組
注意到\(C\)的取值範圍加了絕對值,就說明\(C\)多是負數。那麼若是修改區間內有最大值,就可能會影響最大值。因此說若是咱們不是修改整個塊,那麼就須要從新統計更改後塊的最大值
\(l,r\)同塊的無需多言,直接暴力更改,同時從新維護塊的最大值。時間複雜度\(O(s)\)
\(l,r\)不一樣塊的仍是照樣分紅三部分,頭和尾暴力更改,更新最大值,中間的塊給整個區間加上\(C\),同時最大值能夠直接加\(C\)(能夠簡單推理獲得)。時間複雜度\(O(\dfrac{n}{s}+s)\)
\(l,r\)同塊直接暴力,記得加上給掛在整個塊的值
\(l,r\)不一樣塊也是分紅三部分,中間部分直接與\(b_i\)進行比較,首尾暴力比較(不要漏掉掛在塊上的值)
\(s\)仍是取\(\sqrt{n}\)最優,時間複雜度仍然是\(O(n\sqrt{n})\)
#include<cmath> #include<cstdio> #include<iostream> #define ll long long using namespace std; int n,m,len,l,r,x,opt; ll mx,a[100005],id[100005],b[100005],c[100005]; int read() { int res=0,fh=1;char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') fh=-1;ch=getchar();} while (ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar(); return res*fh; } int main() { freopen("max10.in","r",stdin); freopen("max10.txt","w",stdout); n=read(); len=sqrt(n); for (int i=1;i<=n;++i) { a[i]=read(); id[i]=(i-1)/len+1; b[id[i]]=max(b[id[i]],a[i]); } m=read(); while (m--) { opt=read(); if (opt==1) { l=read();r=read();x=read(); if (id[l]==id[r]) { for (int i=l;i<=r;++i) a[i]+=x; b[id[l]]=-2147483647; for (int i=(id[l]-1)*len+1;id[i]==id[l];++i) b[id[l]]=max(b[id[i]],a[i]+c[id[i]]); } else { for (int i=l;id[i]==id[l];++i) a[i]+=x; for (int i=id[l]+1;i<id[r];++i) c[i]+=x,b[i]+=x; for (int i=r;id[i]==id[r];--i) a[i]+=x; b[id[l]]=b[id[r]]=-2147483647; for (int i=(id[l]-1)*len+1;id[i]==id[l];++i) b[id[l]]=max(b[id[i]],a[i]+c[id[i]]); for (int i=(id[r]-1)*len+1;id[i]==id[r];++i) b[id[r]]=max(b[id[i]],a[i]+c[id[i]]); } } else { mx=-2147483647; l=read();r=read(); if (id[l]==id[r]) { for (int i=l;i<=r;++i) mx=max(mx,a[i]+c[id[l]]); } else { for (int i=l;id[i]==id[l];++i) mx=max(mx,a[i]+c[id[l]]); for (int i=id[l]+1;i<id[r];++i) mx=max(mx,b[i]); for (int i=r;id[i]==id[r];--i) mx=max(mx,a[i]+c[id[r]]); } printf("%lld\n",mx); } } return 0; }
分塊實際上是一種十分暴力的思想,旨在把區間分割開來,因此說在分塊的時候,必定要合理控制塊的大小及個數,並非全部題目\(s\)都是取\(\sqrt{n}\)最優,要根據問題選擇合適的\(s\)
另外分塊也能夠作一些奇奇怪怪的毒瘤題,例如吉司機線段樹……
祝你們新年快樂!!!