【线段树 树状数组】Contest Hunter_4302 Interval GCD

本文介绍了一种解决区间加法与最大公约数查询问题的方法。通过使用差分序列和线段树来维护区间更新,并结合树状数组进行区间内元素的增减操作。最后利用更相减损法原理,巧妙地实现了最大公约数的快速查询。

题意

给出一个长度为NNN的序列,每次有MMM条指令,有两种:
“C“CC lll rrr d”d”d,表示把Al,Al+1,…,ArA_l,A_l+1,…,A_rAl,Al+1,,Ar都加上 d。
“Q“QQ lll r”r”r,表示询问Al,Al+1,…,ArA_l,A_l+1,…,A_rAl,Al+1,,Ar的最大公约数。

思路

根据更相减损法,我们可以知道gcd(x,y)=gcd(x,y−x)gcd(x,y)=gcd(x,y-x)gcd(x,y)=gcd(x,yx)
同理gcd(x,y,z)=gcd(x,y−z,z−y)gcd(x,y,z)=gcd(x,y-z,z-y)gcd(x,y,z)=gcd(x,yz,zy),那么通过这个性质,我们可以用一个差分序列BBB放到线段树里来维护最大公约数,询问的时候就等于求出gcd(Al,ask(1,l+1,r))gcd(A_l,ask(1,l+1,r))gcd(Al,ask(1,l+1,r)),通过差分还可以令它实现区间修改,我们再利用树状数组来维护一开始的序列增加的值。

代码

#include<cstdio>
#include<iostream>
#define ll long long
#define lson(p) ((p) << 1)
#define rson(p) ((p) << 1 | 1)
#define abs(p) ((p) > 0 ? (p) : (-p))
using namespace std; 
ll a[500001], c[500001], x, y, z;
struct treenode{
	int l, r;
	ll v;
}t[2000001];
int n, m;
char check;
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
void build(int p, int l, int r) {
	t[p].l = l;
	t[p].r = r;
	if (l == r) {
		t[p].v = a[l] - a[l - 1];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson(p), l, mid);
	build(rson(p), mid + 1, r);
	t[p].v = abs(gcd(t[lson(p)].v, t[rson(p)].v));//abs防止差分的gcd是负数
}
ll ask(int p, int l, int r) {
	if (t[p].l == l && t[p].r == r) return abs(t[p].v);
	int mid = (t[p].l + t[p].r) >> 1;
	if (r <= mid) return ask(lson(p), l, r);
	else if (l > mid) return ask(rson(p), l, r);
	else {
		ll a = ask(lson(p), l, mid), b = ask(rson(p), mid + 1, r);
		return abs(gcd(a, b));
	}
}
ll find(int x) {
	ll result = 0;
	for (; x; x -= x & -x) result += c[x];
	return result;
}
void add(int x, ll v) {
	for (; x <= n; x += x & -x) c[x] += v;
}
void change(int p, int x, ll v) {
	if (t[p].l == x && t[p].r == x) {
		t[p].v += v;
		return;
	}
	int mid = (t[p].l + t[p].r) >> 1;
	if (x <= mid) change(lson(p), x, v);
	else change(rson(p), x, v);
	t[p].v = abs(gcd(t[lson(p)].v, t[rson(p)].v));
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	build(1, 1, n);
	while (m--) {
		check = getchar();
		while (check < 'A' || check > 'Z') check = getchar();
		if (check == 'Q') {
			scanf("%d %d", &x, &y);
			ll ans = gcd(a[x] + find(x), ask(1, x + 1, y));
			cout<< ans << endl;
		}
		else {
			scanf("%d %d %lld", &x, &y, &z);
			add(x, z);
			add(y + 1, -z);//维护区间
			change(1, x, z);//维护区间
			if (y < n) change(1, y + 1, -z);//防止爆炸
		}
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值