优雅的优化

分块

数列分块入门题
好的博客

分块即为将整个序列分为多个块来处理区间信息,常的操作需支持修改和查询。
一个区间,它一定由一些完整的块和剩余的单点。对于完整的块,我们直接对整个块一起做修改和查询;对于剩余的单点,我们直接暴力修改。

通常块长被分为 \(\sqrt{n}\) ,时间复杂度为 \(O(n\sqrt{n})\)

代码模板:

int n, a[N]; //原数组

int p, pos[N]; //块长 和 每个点属于的块编号
int m, l[N], r[N]; //块的数量 和 每个块的左右端点
int b[N], k[N]; //查询用数组 和 记录块修改的数组

inline void work(){ //预处理
	p = sqrt(n);
	m = ceil(1.0 * n / p); //计算块数
	
	for(int i = 1; i <= m; ++ i){
		l[i] = r[i - 1] + 1;
		r[i] = i * p; //计算块左右端点
		
		for(int j = l[i]; j <= r[i]; ++ j){
			pos[j] = i; //记录当前点块编号
			b[i] = a[i]; //看题目要求
		}
	}
}

inline void add(int x, int y){ //修改区间[x, y]
	for(int i = x; i <= min(y, r[pos[x]]); ++ i){ //左剩余区间
		a[i] + ? //看题目要求
	}
	
	if(pos[x] != pos[y]){ //左右剩余不重
		for(int i = max(l[pos[y]], x); i <= y; ++ i){ //右剩余区间
			a[i] + ? //同上
		}
	}
	
	for(int i = pos[x] + 1; i <= pos[y] - 1; ++ i){ //整块修改
		k[i] + ?//同上
	}
}

inline int ask(int x, int y){ //查询区间
	int sum = 0;
	
	for(int i = x; i <= min(y, r[pos[x]]); ++ i){ //左剩余区间
		sum += a[i]? //看题目要求
	}
	
	if(pos[x] != pos[y]){ //左右剩余不重
		for(int i = max(l[pos[y]], x); i <= y; ++ i){ //右剩余区间
			sum += a[i]? //同上
		}
	}
	
	for(int i = pos[x] + 1; i <= pos[y] - 1; ++ i){ //整块修改
		sum += k[i]? //同上
	}
	
	return sum;
}

通常使用分块算法的数据范围为 \(n <= 1e5\) 或是 \(5e4\) 左右

st表

知乎 - st表
OI Wiki - st表

\(O(nlogn)\) 预处理,\(O(1)\) 查询的复杂度而闻名,用于线性求静态区间信息。
主体是运用倍增的思想,\(st[i][j]\) 维护区间 \((i, i + (1 << j) - 1)\) 的信息,由 \(st[i][j - 1]\)\(st[i + (1 << (j - 1))][j - 1]\) 转移而来
求st表我们还需要一个 \(lg\) 数组,表示 \(log_2(i)\)
具体理解看代码吧

int lg[N];
int st[N][22];

inline int ask(int l, int r){
	int k = lg[r - l + 1];
	return max(st[l][k], st[r - (1 << k) + 1][k]);
	// 查询区间最大值
}

int main(){
	for(int i = 1; i <= n; ++ i){
		st[i][0] = a[i];
	}

	for(int i = 2; i <= n; ++ i){
		lg[i] = lg[i >> 1] + 1;
	}

	for(int i = 1; i <= lg[n]; ++ i){
		for(int j = 1; j + (1 << i) - 1 <= n; ++ j){
			st[j][i] = max(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
			// 此处为求区间最大值
		}
	}
}

st表求LCA

CSDN - st表求LCA
众所周知,dfs序……算了,我不会讲,看代码
\(O(1)\) 的查询,用过都说好

int dfn[N << 1], cnt, d[N];
//d 用来存每个节点第一次出现的位置
int lg[N << 1], st[N << 1][20];
// st 表维护区间深度最小值

inline void Dfs(int now, int fath){
	dfn[++ cnt] = now;
	st[cnt][0] = now;
	d[now] = cnt;
	
	for(int i = head[now]; i; i = e[i].nxt){
		int v = e[i].to;
		
		if(v != fath){
			Dfs(v, now);
			
			dfn[++ cnt] = now;
			st[cnt][0] = now;
		}
	}
	
	return ;
}

inline int Lca(int x, int y){
	if(x > y) swap(x, y);
	int k = lg[y - x + 1];
	if(dep[st[x][k]] < dep[st[y - (1 << k) + 1][k]]){
		return st[x][k];
	}
	else{
		return st[y - (1 << k) + 1][k];
	}
}

int main(){
	Dfs(1, 0);
	
	for(int i = 2; i <= cnt; ++ i){
		lg[i] = lg[i >> 1] + 1;
	}
	
	//这样处理 st表 查询时可以直接返回节点编号
	for(int i = 1; i <= lg[cnt]; ++ i){
		for(int j = 1; j + (1 << i) - 1 <= cnt; ++ j){
			if(dep[st[j][i - 1]] < dep[st[j + (1 << (i - 1))][i - 1]]){
				st[j][i] = st[j][i - 1];
			}
			else{
				st[j][i] = st[j + (1 << (i - 1))][i - 1];
			}
		}
	}
}
posted @ 2023-10-03 20:59  Biuld  阅读(8)  评论(0编辑  收藏  举报