Bash and a Tough Math Puzzle(线段树维护区间公约数)

直接给中文题意吧:

给定长度为n的数组a1,a2,…,an。
q次操作。操作分为两种:

  • 1 l r x,若能在a[l]~a[r]中至多修改一个数的情况下,使得gcd(a[l],a[l+1],…,a[r])=x,输出YES,否则输出NO。注意:我们不需要进行实际的改动。
  • 2 i y,将a[i]修改为y。

Input

第一行为一个整数n(1 ≤ n ≤ 5*105)。
第二行为n个整数,表示数组a(1 ≤ ai ≤ 109)。
第三行为一个整数q(1 ≤ q ≤ 4*105)。
接下来q行,每行一个操作。
对于第一类操作,1 ≤ l ≤ r ≤ n, 1 ≤ x ≤ 109。
对于第二类操作,1 ≤ i ≤ n,1 ≤ y ≤ 109。

Output

对于每个第一类操作,若可行,输出'YES',否则输出'NO'。

输入:

5
1 2 3 4 5
6
1 1 4 2
2 3 6
1 1 4 2
1 1 5 2
2 5 10
1 1 5 2

输出:

NO
YES
NO
YES

分析:通过题意我们容易想到用线段树维护最大公约数,但关键是如何判断至多修改一个点的情况下,能不能使得区间最大公约数等于x,其实就是转化为判断区间中有几个数不是x的倍数,如果有两个及两个以上的数不是x的倍数,那么输出NO,否则输出YES,注意本题需要剪枝,防止区间中存在很多个数都不是x的倍数,那么我们每一个数都要遍历到叶子节点,那么必TLE,其他就没什么了,下面上代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=3e6+10;
int g[N],a[N],l[N],r[N];
int ans;
void pushup(int id)
{
	g[id]=__gcd(g[id<<1],g[id<<1|1]);
}
void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;g[id]=0;
	if(L==R)
	{
		g[id]=a[L];
		return ;
	}
	int mid=L+R>>1;
	build(id<<1,L,mid);
	build(id<<1|1,mid+1,R);
	pushup(id);
}
void update_point(int id,int x,int val)
{
	if(l[id]==r[id])
	{
		g[id]=val;
		return ;
	}
	int mid=l[id]+r[id]>>1;
	if(x<=mid) update_point(id<<1,x,val);
	else update_point(id<<1|1,x,val);
	pushup(id);
}
void query_interval(int id,int L,int R,int x)
{
	if(ans>1) return ;//剪枝,否则会TLE 
	if(l[id]>R||r[id]<L) return ;
	if(l[id]==r[id])
	{
		ans+=(g[id]%x!=0);
		return ;
	}
	if(l[id]>=L&&r[id]<=R)
	{
		if(g[id]%x==0) return ;//如果整个区间的最大公约数能够被x整除,那么这个区间的所有数都是x的倍数 
	}
	query_interval(id<<1,L,R,x);
	query_interval(id<<1|1,L,R,x);
}
int main()
{
	int n,q;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,1,n);
	cin>>q;
	int ll,rr,x,op;
	while(q--)
	{
		scanf("%d",&op);
		if(op==1)
		{
			ans=0;//不要忘记每次清空ans 
			scanf("%d%d%d",&ll,&rr,&x);
			query_interval(1,ll,rr,x);
			if(ans>1) puts("NO");
			else puts("YES");
		}
		else
		{
			scanf("%d%d",&ll,&x);
			update_point(1,ll,x);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值