【牛客网】小白月赛16 H 小阳的贝壳

题目链接:https://ac.nowcoder.com/acm/contest/949/H

题意:求区间gcd,带修改。

题解:线段树gcd,差分,单点修改,区间询问。注意gcd满足的性质,可以使其差分,这样区间修改就可以转话成两次单点修改。

//线段树gcd,差分,单点修改,区间询问。

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const int maxn = 100005;
ll input[maxn];
ll a[maxn];
ll c[maxn * 4];
int n, m;
int lowbit(int x)
{
	return x & -x;
}
void add(int x, int val)
{
	while (x <= n)
	{
		c[x] += val;
		x += lowbit(x);
	}
}
ll sum(ll x)
{
	ll ans = 0;
	while (x)
	{
		ans += c[x];
		x -= lowbit(x);
	}
	return ans;
}
ll  _gcd(ll a, ll b)
{
	if (b == 0)
		return a;
	else
		return __gcd(b, a%b);
}
struct node
{
	ll l, r;                                        //左右区间
	ll MAX, MIN, g;									//最大,最小,gcd 
}tree[maxn * 4];                                    //4倍空间

void build(int i, int l, int r)                     //递归建树
{
	tree[i].l = l; tree[i].r = r;
	if (l == r)                                     //如果这个节点是叶子节点
	{
		tree[i].MAX = input[l];
		tree[i].MIN = input[l];
		tree[i].g = abs(input[l]);
		return;
	}
	int mid = (l + r) >> 1;
	build(i * 2, l, mid);                           //分别构造左子树和右子树
	build(i * 2 + 1, mid + 1, r);
	tree[i].g = __gcd(tree[i * 2].g, tree[i * 2 + 1].g);
	tree[i].MAX = max(tree[i * 2].MAX, tree[i * 2 + 1].MAX);
	tree[i].MIN = min(tree[i * 2].MIN, tree[i * 2 + 1].MIN);
}
ll search_max(int i, ll l, ll r)
{
	if (tree[i].l >= l && tree[i].r <= r)			//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
		return tree[i].MAX;
	if (tree[i].r<l || tree[i].l>r)  return 0;       //如果这个区间和目标区间毫不相干,返回0
	return max(search_max(i * 2, l, r), search_max(i * 2 + 1, l, r));
}
ll search_min(int i, ll l, ll r)
{
	if (tree[i].l >= l && tree[i].r <= r)			//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
		return tree[i].MIN;
	if (tree[i].r<l || tree[i].l>r)  return 0;       //如果这个区间和目标区间毫不相干,返回0
	return min(search_min(i * 2, l, r), search_min(i * 2 + 1, l, r));
}
ll search_gcd(int i, ll l, ll r)
{
	if (tree[i].l >= l && tree[i].r <= r)			//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
		return tree[i].g;
	if (tree[i].r<l || tree[i].l>r)  return 0;		//如果这个区间和目标区间毫不相干,返回0
	return __gcd(search_gcd(i * 2, l, r), search_gcd(i * 2 + 1, l, r));
}
void add(int i, ll l, ll r, ll k)					//其实这点是单点修改,不用区间 
{
	if (tree[i].r == r && tree[i].l == l)			//如果当前区间被完全覆盖在目标区间里
	{
		tree[i].MAX += k;
		tree[i].MIN += k;
		tree[i].g = abs(input[l]);					//直接更新根节点 
		return;
	}
	if (tree[i * 2].r >= l)
		add(i * 2, l, r, k);
	if (tree[i * 2 + 1].l <= r)
		add(i * 2 + 1, l, r, k);
	tree[i].g = __gcd(tree[i * 2].g, tree[i * 2 + 1].g);
	tree[i].MAX = max(tree[i * 2].MAX, tree[i * 2 + 1].MAX);
	tree[i].MIN = min(tree[i * 2].MIN, tree[i * 2 + 1].MIN);
	return;
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", &a[i]);
		input[i] = a[i] - a[i - 1];
	}
	build(1, 1, n);
	for (int i = 1; i <= n; i++)					//加入树状数组,维护差分 
	{
		add(i, input[i]);
	}
	while (m--)
	{
		int tp;
		scanf("%d", &tp);
		if (tp == 1)
		{
			ll x, y, k;
			scanf("%lld%lld%lld", &x, &y, &k);
			input[x] += k;			//根节点修改 
			add(1, x, x, k);		//父节点修改 
			add(x, k);				//树状数组维护差分区间 
			if (y + 1 <= n)
			{
				input[y + 1] -= k;
				add(1, y + 1, y + 1, -k);
				add(y + 1, -k);
			}
		}
		else if (tp == 2)
		{
			ll x, y;
			scanf("%lld%lld", &x, &y);
			if (x == y)
				cout << 0 << endl;
			else
			{
				ll ans = abs(max(abs(search_min(1, x + 1, y)), abs(search_max(1, x + 1, y))));//寻找绝对值最大的差分 
				printf("%lld\n", ans);
			}
		}
		else
		{
			ll x, y;
			scanf("%lld%lld", &x, &y);
			printf("%lld\n", __gcd(sum(x), search_gcd(1, x + 1, y)));
		}
	}
	return 0;
}
发布了119 篇原创文章 · 获赞 20 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_40727946/article/details/97244345