【线段树】CF #458 (Div. 1 + Div. 2, combined) D Bash and a Tough Math Puzzle

经典数据结构之线段树
介于复制粘贴文本过来后甚是难看的缘故,截个屏搬图片过来吧,嘿嘿
再来一个原网站地址:D-Bash and a Tough Math Puzzle
【线段树】CF #458 (Div. 1 + Div. 2, combined) D Bash and a Tough Math Puzzle_第1张图片
【线段树】CF #458 (Div. 1 + Div. 2, combined) D Bash and a Tough Math Puzzle_第2张图片
吐槽:光光学长讲太好了,讲的非常清楚,我竟然听懂了,哈哈哈,推一波他博客:光光学长博客

在这里我再来梳理一遍思路,以加深印象。
首先需要明确题意:给一个数组,在这里我们不妨用 a r r arr arr来表示;给定一个数 q q q,表示 q q q次询问,每一次询问可对应一种操作,一共有两种操作: 1 : 1: 1:询问区间 [ l l l, r r r]中是否可以通过至多更改一个元素来满足给定的 x x x是这个区间内所有数的最大公约数。 2 : 2: 2:更改原数组 a r r arr arr中的一个元素。

思路梳理:显然就是线段树的操作,对于第 2 2 2种操作就不过多解释了,就是线段树的单点修改操作。重点来说一下第 1 1 1种操作,要想满足 x x x是区间[ l l l, r r r]中在经过至多一次更改后是所有元素的最大公约数,假设这个区间内有 n n n个元素,则必定要满足至少有 n − 1 n-1 n1个元素能够整除 x x x,即满足 a r r [ i ] arr[i] arr[i]% x x x==0的元素个数大于等于 n − 1 n-1 n1个.

想必到这里肯定也会产生一个疑问,全部能够整除 x x x就一定是最大公约数吗?例如下面的这个例子:
如果[ l l l, r r r]中的元素是:
4 4 4 8 8 8 12 12 12 16 16 16. 明显这些元素在不进行更改的情况下最大公约数是 4 4 4.

而此时如果 x x x的值是 2 2 2,对于区间内每个元素都能够整除,但 2 2 2并不是最大公约数,想一下,这个是没有进行更改操作的情况,如果进行一次更改操作呢,显然一定能够满足条件。
所以对于这个问题就不需要顾虑了,只要能有大于等于 n − 1 n-1 n1个元素满足% x x x==0就可。

注意下方的 n n n非上方的 n n n,下方的 n n n指的是 a r r arr arr数组中的元素个数;,至于为什么不换个变量,因为我爱 n n n,爱的深沉,哈哈哈哈哈

但是想一想,在查询的时候最多要判断 n − 1 n-1 n1元素的对吧,这样的话时间复杂度就是 O ( n ∗ l o g n ) O(n*logn) O(nlogn),时间复杂度过大,不太好,经过学长的测试也确实是超时了,接下来考虑一下优化,但是在哪里优化呢, l o g n logn logn这个肯定是优化不了了,所以就考虑将n这个值缩小;怎么缩小呢?来,下方见解释:
在判断的时候判断 2 2 2个元素不能整除 x x x总比判断 n − 1 n-1 n1个元素能整除 x x x快吧,具体的话,我也说不太清楚,从表面上看确实是这样。

好,这样就给化简为查询一个区间内有多少个不能整除 x x x,大于或等于 2 2 2个就t跳出输出 N O NO NO,否则,输出 Y E S YES YES.
对了,树中就存储一段区间的GCD.

写的有点啰嗦,别介哈,我都不介的,哼!

上代码:

#include
using namespace std;
const int N = 500010;
int a[N], n;
struct segment_tree {
	int l, r, val;//val记录区间内所有数的最大公约数
}tree[4 * N];
//求GCD
int gcd(int x, int y)
{
	if (y == 0) return x;
	else return gcd(y, x % y);
}
//更新父节点
void pushup(int node)
{
	tree[node].val = gcd(tree[2 * node].val, tree[2 * node + 1].val);
}
//建树
void build_tree(int node, int start, int ends)
{
	tree[node].l = start, tree[node].r = ends;
	if (start==ends)
	{
		tree[node].val = a[start];
		return;
	}
	int mid = (tree[node].l + tree[node].r) / 2;
	build_tree(2 * node, start, mid);
	build_tree(2 * node + 1, mid + 1, ends);
	pushup(node);
}
//更新叶子节点
void update(int node, int idx, int val)
{
	if (tree[node].l == idx && tree[node].r == idx)
	{
		tree[node].val = val;
		return;
	}
	int mid = (tree[node].l + tree[node].r) / 2;
	if (idx <= mid) update(2 * node, idx, val);
	else update(2 * node + 1, idx, val);
	pushup(node);
}
//查询操作
int tmp;
void query(int node, int L, int R,int x)
{
	if (tmp >= 2) return;
	if (tree[node].l==tree[node].r)
	{
		if (tree[node].val % x) tmp++;
		return;
	}
	int mid = (tree[node].l + tree[node].r) / 2;
	if (L <= mid && tree[node * 2].val % x) query(2 * node, L, R, x);
	if (R > mid&& tree[node * 2 + 1].val% x) query(2 * node + 1, L, R, x);
}
int main()
{
	scanf("%d",&n);
	for (int i = 1;i <= n;i++)
	{
		scanf("%d",&a[i]);
	}
	build_tree(1, 1, n);
	int q;
	scanf("%d",&q);
	while (q--)
	{
		int type;
		scanf("%d",&type);
		if (type == 1)
		{
			int l, r, x;
			tmp = 0;
			scanf("%d%d%d",&l,&r,&x);
			query(1, l, r, x);
			if (tmp <= 1) puts("YES");
			else puts("NO");
		}
		else {
			int idx, val;
			scanf("%d%d",&idx,&val);
			update(1, idx, val);
		}
	}
	return 0;
 
}

你可能感兴趣的:(【线段树】CF #458 (Div. 1 + Div. 2, combined) D Bash and a Tough Math Puzzle)