ContestHunter4302 Interval GCD(差分+线段树+gcd性质)

题目

长度为n(n<=5e5)的数组a[],m(m<=2e5)次操作,操作分两种

①对[l,r]区间加d   ②询问[l,r]区间的gcd

题目保证ai在任何操作之后都为不超过2^{62}-1的正整数

思路来源

https://blog.csdn.net/forever_dreams/article/details/85222870

题解

注意到gcd(x,y,z)==gcd(x,y-x,z-y),推广到n个数也成立

证明的话,就设它们的gcd为k,然后用x,y,z分别去除以k,

所得数是三个互质的数,那么它们作差也互质,

有了这个性质之后,就可以直接维护差分数组,

化区间修改为单点修改,a[l]加上d,a[r+1]减去d

询问gcd(l,l+1,...,r)=gcd(a[l],a[l+1]-a[l],...,a[r]-a[r-1]),

这样,a[l]用BIT维护差分数组前缀和求,后面的用线段树维护差分数组gcd

trick:差分值可能是负的,好在gcd只是多了负号,取绝对值即可

代码

#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int n,m,l,r;
char op[5];
//tr[]:维护差分数组前缀和
//gcd[]:维护差分数组区间gcd 
ll res,v,a[maxn],tr[maxn],gcd[maxn*5];
void add(int x,ll v)
{
	for(int i=x;i<=n;i+=i&-i)
	tr[i]+=v;
}
ll sum(int x)
{
	ll ans=0;
	for(int i=x;i>0;i-=i&-i)
	ans+=tr[i];
	return ans; 
}
void pushup(int p)
{
	gcd[p]=__gcd(gcd[p<<1],gcd[p<<1|1]);
}
void build(int p,int l,int r)
{
	if(l==r)
	{
		gcd[p]=a[l];
		return;
	}
	int m=l+r>>1;
	build(p<<1,l,m);
	build(p<<1|1,m+1,r); 
	pushup(p);
}
void update(int p,int l,int r,int pos,ll v)
{ 
	if(l==r)
	{
		gcd[p]+=v;
		return;
	}
	int m=l+r>>1;
	if(pos<=m)update(p<<1,l,m,pos,v);
	else update(p<<1|1,m+1,r,pos,v);
	pushup(p);
}
ll ask(int p,int l,int r,int ql,int qr)
{
	if(ql>qr)return 0;
	if(ql<=l&&r<=qr)return gcd[p];
	ll res=0;
	int m=l+r>>1;
	if(ql<=m)res=__gcd(res,ask(p<<1,l,m,ql,qr));
	if(qr>m)res=__gcd(res,ask(p<<1|1,m+1,r,ql,qr));
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	scanf("%lld",&a[i]);
	for(int i=n;i>=1;--i)
	{ 
		a[i]=a[i]-a[i-1];
		add(i,a[i]); 
	} 
	build(1,1,n);
	while(m--)
	{
		scanf("%s",op);
		if(op[0]=='C')
		{
			scanf("%d%d%lld",&l,&r,&v);
			add(l,v);
			update(1,1,n,l,v);
			if(r+1<=n)
			{
				add(r+1,-v);
				update(1,1,n,r+1,-v);
			}
		}
		else if(op[0]=='Q')
		{
			scanf("%d%d",&l,&r);
			printf("%lld\n",abs(__gcd(sum(l),ask(1,1,n,l+1,r))));
		}
	}
	return 0;
}

 

你可能感兴趣的:(线段树(权值线段树)/树状数组,差分)