分块&莫队
更优美的暴力
众所周知,暴力经常慷慨的给 OIer 优美的部分分,而更优美的暴力优化分块便是强到碾压线段树的算法。
根据「骗分导论」里面对暴力的描述,暴力并不繁杂,所以分块也并不难。
思想
前文说到分块可以碾压线段树,那么我们以线段树的板子题为例题。
【模板】线段树 1
已知一个数列 \(a\) ,长度为 \(n\) 你需要进行 \(m\) 次下面的两种操作:
- 将区间 \([x,y]\) 中的每一个数加上 \(k\)。
 - 求出区间 \([x,y]\) 中的每一个数的和。
 \(1 \le n, m \le {10}^5\) 。
保证任意时刻数列中所有元素的绝对值之和 \(\le {10}^{18}\)。
既然是暴力,我们从最朴素的循环开始考虑。
对于每一个操作,都将区间进行枚举,从而求值,显然这是拿不到满分的。
而我们可以将序列分成若干组,与线段树与树状数组分 \(2\) 的正整数次幂不同的是,我们将序列分成长度为 \(t\) 的若干组区间,我们将其称为「块」,然后记录每一个块的左右端点 \(l[i],r[i]\),再暴力处理每个块的和 \(sum[i]\) 。
此时对于每组查询,有两种情况:
- 当 \([x,y]\) 在某个块内,则直接暴力,复杂度为 \(O(t)\)。
 - 当 \([x,y]\) 至少横跨两个块及以上,则可以将区间分为两到三个部分。
1 . 左端点 \(x\) 所在的块,复杂度为 \(O(t)\)。
2 . 右端点 \(y\) 所在的块,复杂度为 \(O(t)\)。
3 . 两者之间所被包含的所有块,复杂度为 \(O(\frac{n}{t})\)。 
因为左右两个区间不一定被全部覆盖,此时我们可以暴力求和。
而中间的区块由于已经预处理过 \(sum\) ,所以可以直接加。
而区间加法也与其类似:
- 维护一个 \(add\) 数组来存储对于每个块的加法操作
1 . 当 \([x,y]\) 在某个块内,直接暴力加,复杂度为 \(O(t)\)。
2 . 当 \([x,y]\) 横跨块时,对于左右两个端点所在的块,直接暴力加,而对于中间完全被覆盖的块,把加值加在 \(add\) 上,复杂度为 \(O(\frac{n}{t}+2t)\),省略常数为 \(O(\frac{n}{t}+t)\)。 
在暴力加时,需要更新 \(a[i]\) 的值和 \(sum[i]\) 的累加。
而在查询时,需要把 \(add\) 的值也加上,注意暴力加单个是直接加,而在加一个块时,需将其乘上块长 \(t\)。
而根据均值不等式或目测法贪心来看,\(t\) 的取值应该是 \(\sqrt n\) 。
所以查询的复杂度是 \(O(t)\) ,修改也是 \(O(t)\)。
此时,我们就得到了 \((n\sqrt n)\) 这个优秀的复杂度。
虽不及线段树,但代码比线段树少很多。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
long long a[N],l[N],r[N],sum[N],id[N],add[N];//id 是原数组在块上的编号 
int n,t,m; 
int main(){
	cin>>n>>m;
	int t=sqrt(n);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i*t<=n;i++){//预处理 l,r,sum,id 
		l[i]=(i-1)*t+1;
		r[i]=i*t;
		for(int j=l[i];j<=r[i];j++){
			sum[i]+=a[j];
			id[j]=i;
		}
	}
	l[t+1]=t*t+1;//由于int下取整,所以我们单独考虑一下最后一个块 
	r[t+1]=n;
	for(int i=t*t+1;i<=n;i++){
		sum[t+1]+=a[i];
		id[i]=t+1;
	}
	while(m--){
		int op,x,y,k;
		cin>>op>>x>>y;
		if(op==1){//区间加 
			cin>>k;
			if(id[x]==id[y]){//在一个块内 
				for(int i=x;i<=y;i++)
					a[i]+=k;
				sum[id[x]]+=(y-x+1)*k;
			}
			else{
				for(int i=x;i<=r[id[x]];i++){//左端点的块 
					a[i]+=k;
					sum[id[x]]+=k;
				}
				for(int i=y;i>=l[id[y]];i--){//右端点的块 
					a[i]+=k;
					sum[id[y]]+=k;
				}
				for(int i=id[x]+1;i<=id[y]-1;i++)//中间完整的块 
					add[i]+=k;
			}
		}
		else{//区间查询 
			long long ans=0;//注意long long 
			if(id[x]==id[y]){//在一个块内 
				for(int i=x;i<=y;i++)
					ans+=a[i]+add[id[x]];
				cout<<ans<<"\n";
			}
			else{
				for(int i=x;i<=r[id[x]];i++)
					ans+=a[i]+add[id[x]];//左端点的块 
				for(int i=y;i>=l[id[y]];i--)
					ans+=a[i]+add[id[y]];//右端点的块 
				for(int i=id[x]+1;i<=id[y]-1;i++)
					ans+=sum[i]+add[i]*t;//中间完整的块 
				cout<<ans<<"\n";
			}
		}
	}
	return 0;
} 
                    
                
                
            
        
浙公网安备 33010602011771号