【线段树】区间gcd

这篇博客介绍了如何使用线段树解决区间gcd查询和修改的问题。在每次查询时,不仅需要维护每个区间的gcd,还要记录区间内不为gcd倍数的数的数量。当遇到gcd等于目标值x时,可以直接判断查询失败。文章提供了详细的题解思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

初始有n个数。
有两个操作

  1. L R x 查询[L,R]的gcd是不是x,在查询过程中可以任意篡改一个数(不是真正的修改)
  2. pos x 将pos位置的值修改为x

题解

因为每次可以修改一个数,所以就不能只维护每个区间的gcd,还应该维护该区间不是x倍数的数的个数,如果 >= 2就失败。
这里有个优化就是对于 gcd == x的区别就没必要再查询下去了,因为不存在不是x倍数的区间。

代码

#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 5e5+5;
int c[maxn<<2];
int gcd(int a,int b) {return b == 0 ? a : gcd(b,a%b);}
void build(int l, int r, int rt) {
	if(l == r) {
		scanf("%d", &c[rt]);
		return;
	}
	int mid = (l+r)>>1;
	build(lson);
	build(rson);
	// cout << c[rt<<1] <<" " << c[rt<<1|1] << endl;
	c[rt] = gcd(c[rt<<1], c[rt<<1|1]);
}
void update(int L, int v,int l, int r, int rt) {
	if(l == r) {
		c[rt] = v;
		return;
	}
	int mid = (l+r)>>1;
	if(mid >= L) 
		update(L,v,lson);
	if(mid < L) 
		update(L,v,rson);
	c[rt] = gcd(c[rt<<1], c[rt<<1|1]);
}
int query(int L, int R,int x,int l, int r,int rt) {
	if(L > r || R < l) return 0;
	if(l == r) {
		return c[rt]%x != 0;
	}
	// cout << rt << endl;
	int ret = 0;
	int mid = (l+r)>>1;
	if(c[rt<<1]%x) {
		ret += query(L,R,x,lson);
		if(ret > 1) return 2;
	}
	if(c[rt<<1|1]%x) {
		ret += query(L,R,x,rson);
		if(ret > 1) return 2;
	}
	return ret;
}
int main() {
	int n;
	scanf("%d", &n);
	build(1,n,1);
	int q;
	scanf("%d",&q);
	int opt,l,r,v;
	for(int i = 0; i < q; ++i) {
		scanf("%d%d%d", &opt, &l, &r);
		if(opt == 1) {
			scanf("%d", &v);
			if(query(l,r,v,1,n,1)>1) puts("no");
			else puts("yes");
		} else {
			update(l,r,1,n,1);
		}
	}
	return 0;
}
### Java 中线段树的实现与应用 #### 线段树简介 线段树是一种二叉搜索树,它将一个区间划分为多个小区间,在这些小区间上执行快速查询和更新操作。对于长度为 \( n \) 的数组,构建线段树的时间复杂度为 \( O(n) \),而单次查询或更新的操作时间复杂度均为 \( O(\log n) \)[^1]。 #### 构建线段树 为了创建一棵用于求解区间线段树,可以定义如下结构: ```java class SegmentTree { int[] tree; public SegmentTree(int[] array){ this.tree = new int[array.length * 4]; build(array, 0, array.length - 1, 1); } } ``` 这里 `tree` 数组用来存储线段树节点的数据,大小通常是原数组长度的四倍以确保有足够的空间容纳所有可能产生的子节点;`build()` 方法负责初始化这棵树并填充数据。 #### 更新操作 当需要修改某个位置上的数值时,可以通过递归方式调整受影响的部分: ```java void update(int pos, int val, int start, int end, int nodeIndex) { if (start == end) { // Leaf Node tree[nodeIndex] += val; return ; } int mid = getMid(start,end); if(pos<=mid) update(pos,val,start,mid,nodeIndex*2); else update(pos,val,mid+1,end,nodeIndex*2+1); tree[nodeIndex]=tree[nodeIndex*2]+tree[nodeIndex*2+1]; } ``` 此函数接收待更改的位置 (`pos`) 和新值 (`val`) ,以及当前处理范围(`start`, `end`) 和对应的索引(`nodeIndex`). 如果目标位于左侧,则继续向左子树深入;反之则转向右侧。最后一步是重新计算父节点的新总和。 #### 查询操作 要获取指定范围内元素之和,同样采用分治策略遍历整棵或多棵分支直到找到匹配项为止: ```java int queryRange(int qs,int qe,int ss,int se,int index){ if(qs>se || qe<ss){return 0;} // No overlap if(qs<=ss && qe>=se){return tree[index];} // Complete Overlap int m=getMid(ss,se); return queryRange(qs,qe,ss,m,index*2)+queryRange(qs,qe,m+1,se,index*2+1); } ``` 这段代码实现了对给定区间 `[qs, qe]` 进行求和的功能。如果请求完全覆盖了现有片段,则直接返回该部分的结果;如果有交集但不完全重合,则分别访问左右孩子来累积答案。 #### 应用场景 线段树广泛应用于解决涉及频繁变化的一维数组上的动态区间问题,比如: - 动态维护序列中的最大/最小值; - 计算连续子串的最大公约数(GCD) 或者其他聚合属性; - 多版本快照管理等高级功能。 通过上述方法可以在保持高效的同时轻松应对各种复杂的区间运算需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值