下面介绍一种暴力,当然呢这种暴力比一般快很多。
先说一下这个暴力的思路。对于一个长度为\(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