下面介紹一種暴力,當然呢這種暴力比一般快很多。
先說一下這個暴力的思路。對於一個長度為\(n\)的陣列\(a\),可以把陣列\(a\)分成\(k\)塊,其中每一塊的長度為\(len\),當然最後一行除外因為\(n\)可能不是\(k\)的倍數,最後一塊的長度可以不是\(len\)。
那麼就可以用這些塊來維護資料。
那麼對於一個區間\([l..r]\)可以分成兩種情況:
\(1\).區間\([l..r]\)在同一個塊裡也就是:
這種情況下可以暴力列舉\(l\)到\(r\)即可。
\(2\).區間\([l..r]\)不在同一個塊裡也就是:、
這種情況下首先應該考慮中間整塊的部分(圖中\(3,4f\)),然後就是\(l\)和\(r\)中的殘塊也就是\(2\)和\(5\)中間的那一部分。
例題I 線段樹1
點選檢視題面
## 題目描述如題,已知一個數列,你需要進行下面兩種操作:
- 將某區間每一個數加上 \(k\)。
- 求出某區間每一個數的和。
輸入格式
第一行包含兩個整數 \(n, m\),分別表示該數列數字的個數和操作的總個數。
第二行包含 \(n\) 個用空格分隔的整數,其中第 \(i\) 個數字表示數列第 \(i\) 項的初始值。
接下來 \(m\) 行每行包含 \(3\) 或 \(4\) 個整數,表示一個操作,具體如下:
1 x y k
:將區間 \([x, y]\) 內每個數加上 \(k\)。2 x y
:輸出區間 \([x, y]\) 內每個數的和。
輸出格式
輸出包含若干行整數,即為所有操作 2 的結果。
樣例 #1
樣例輸入 #1
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
樣例輸出 #1
11
8
20
提示
對於 \(30\%\) 的資料:\(n \le 8\),\(m \le 10\)。
對於 \(70\%\) 的資料:\(n \le {10}^3\),\(m \le {10}^4\)。
對於 \(100\%\) 的資料:\(1 \le n, m \le {10}^5\)。
保證任意時刻數列中所有元素的絕對值之和 \(\le {10}^{18}\)。
【樣例解釋】
現在這一題是要支援區間求和,很顯然用分塊來維護區間和,那麼可以用一個陣列\(s\)表示當前塊的最大和。
對於每一次的詢問仍舊分為兩部分\(l\)和\(r\)在同一塊裡和\(l\)和\(r\)不在同一塊裡。
那麼程式碼就是:
int query(int l,int r){
int sid=id[l],eid=id[r];//表示開始塊編號和結束塊編號
if(sid==eid){//l,r在同一塊裡
int sum=0;
for(int i=l;i<=r;i++)sum+=a[i]+b[sid];//這裡的a是原陣列,b是每一塊的懶標記
return sum;
}
int sum=0;
for(int i=l;id[i]==sid;i++)sum+=a[i]+b[sid];//l所對應的殘塊
for(int i=sid+1;i<eid;i++)sum+=s[i];//l,r中間的真快,s是每一塊的和
for(int i=r;id[i]==eid;i--)sum+=a[i]+b[eid];//r所對應的殘塊
return sum;
}
修改的方法和詢問是一樣的
void add(int l,int r,int x){
int sid=id[l],eid=id[r];
if(sid==eid){
for(int i = l;i <= r;i ++ )s[sid] += x,a[i] += x;
return;
}
for(int i = l;id[i] == id[l];i ++ ) s[id[l]] +=x,a[i] +=x;
for(int i = sid + 1;i < eid;i ++ )s[i] += len * x,b[i] += x;
for(int i = r;id[i] == id[r];i -- ) s[id[r]] +=x, a[i] += x;
}
完整程式碼:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int a[N],b[N],s[N];
int id[N];
int n,m,len;
int query(int l,int r){
int sid=id[l],eid=id[r];
if(sid==eid){
int sum=0;
for(int i=l;i<=r;i++)sum+=a[i]+b[sid];
return sum;
}
int sum=0;
for(int i=l;id[i]==sid;i++)sum+=a[i]+b[sid];
for(int i=sid+1;i<eid;i++)sum+=s[i];
for(int i=r;id[i]==eid;i--)sum+=a[i]+b[eid];
return sum;
}
void add(int l,int r,int x){
int sid=id[l],eid=id[r];
if(sid==eid){
for(int i = l;i <= r;i ++ ){
s[sid] += x;
a[i] += x;
}
return;
}
for(int i = l;id[i] == id[l];i ++ ) s[id[l]] +=x,a[i] +=x;
for(int i = r;id[i] == id[r];i -- ) s[id[r]] +=x, a[i] += x;
for(int i = sid + 1;i < eid;i ++ ){
s[i] += len * x;
b[i] += x;
}
}
signed main(){
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
int cnt=(i-1)/len+1;
id[i]=cnt;
s[cnt]+=a[i];
}
while(m--){
int ops,l,r,c;cin>>ops>>l>>r;
if(ops==1)cin>>c,add(l,r,c);
else cout<<query(l,r)<<'\n';
}
return 0;
}
例題II
點選檢視題面
# Anton and Permutation題面描述
有一個長度為 \(n\) 的排列,初始為 \(1,2,\dots,n\)。
現在對其進行 \(k\) 次操作,每次操作都是交換序列中的某兩個數。對於每一個操作,回答當前序列中有多少個逆序對。
樣例 #1
樣例輸入 #1
5 4
4 5
2 4
2 5
2 2
樣例輸出 #1
1
4
3
3
樣例 #2
樣例輸入 #2
2 1
2 1
樣例輸出 #2
1
樣例 #3
樣例輸入 #3
6 7
1 4
3 5
2 3
3 3
3 6
2 1
5 1
樣例輸出 #3
5
6
7
7
10
11
8