acwing 246 区间最大公约数 线段树+树状数组+差分

27 篇文章 0 订阅
5 篇文章 0 订阅

https://www.acwing.com/problem/content/247/?time=1564925894116
给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:

1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。

2、“Q l r”,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。

对于每个询问,输出一个整数表示答案。

输入格式
第一行两个整数N,M。

第二行N个整数A[i]。

接下来M行表示M条指令,每条指令的格式如题目描述所示。

输出格式
对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围
N≤500000,M≤100000
输入样例:
5 5
1 3 5 7 9
Q 1 5
C 1 5 1
Q 1 5
C 3 3 6
Q 2 4
输出样例:
1
2
4

思路:更相减损术是我国古代求两个数最大公因数的方法。有一结论:gcd(x,y,z,…)=gcd(x,y-x,z-y,…)。因此考虑用线段树来维护原数组的差分数组的gcd。(差分数组:dis[i]=a[i]-a[i-1])这样做有一个很大的好处,就是区间修改转化成了单点修改。证明如下:

根据定义我们有: d i s [ l ] = a [ l ] − a [ l − 1 ] , d i s [ l + 1 ] = a [ l + 1 ] − a [ l ] , … … dis[l]=a[l]-a[l-1],dis[l+1]=a[l+1]-a[l],…… dis[l]=a[l]a[l1],dis[l+1]=a[l+1]a[l], … … , d i s [ r ] = a [ r ] − a [ r − 1 ] , d i s [ r + 1 ] = a [ r + 1 ] − a [ r ] ……,dis[r]=a[r]-a[r-1],dis[r+1]=a[r+1]-a[r] ,dis[r]=a[r]a[r1],dis[r+1]=a[r+1]a[r]那么对原序列的区间修改操作: ∑ k = l r ( a k = a k + d ) \sum_{k=l}^r(a_k=a_k+d) k=lr(ak=ak+d)实际上只影响到了线段树的两个节点: d i s [ l ] = d i s [ l ] + d dis[l]=dis[l]+d dis[l]=dis[l]+d d i s [ r + 1 ] = d i s [ r + 1 ] − d dis[r+1]=dis[r+1]-d dis[r+1]=dis[r+1]d这样复杂度就可以接受了。注意单点修改的时候判断一下r+1与n的大小关系。修改的问题解决了,我们来看看查询的问题。要查询: G C D ( a [ l ] , a [ l + 1 ] , … … , a [ r ] ) GCD(a[l],a[l+1],……,a[r]) GCD(a[l],a[l+1],,a[r])就相当于查询: G C D ( a [ l ] , a [ l + 1 ] − a [ l ] , … … , a [ r ] − a [ r − 1 ] ) GCD(a[l],a[l+1]-a[l],……,a[r]-a[r-1]) GCD(a[l],a[l+1]a[l],,a[r]a[r1])然而我们很容易写出一个线段树的区间查询操作: q u e r y ( l + 1 , r ) = G C D ( a [ l + 1 ] − a [ l ] , … … , a [ r ] − a [ r − 1 ] ) query(l+1,r)=GCD(a[l+1]-a[l],……,a[r]-a[r-1]) query(l+1,r)=GCD(a[l+1]a[l],,a[r]a[r1])因此我们知道知道a[l]的值就可以完成查询操作了。那么怎么维护原序列的值呢?用树状数组就可以了。原因很明显: d i s [ 1 ] + d i s [ 2 ] + … … + d i s [ i ] = a [ i ] dis[1]+dis[2]+……+dis[i]=a[i] dis[1]+dis[2]++dis[i]=a[i]那么问题就解决了,上代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;

const int maxn=5e5+5;

struct node
{
	int l,r;
	ll v;
}tree[maxn<<2];

int n,m;
ll a[maxn];
ll dis[maxn];
ll bit[maxn];

inline ll lowbit(ll x)
{
	return x&(-x);
}

inline void add(int pos,ll v)
{
	for(int i=pos;i<=n;i+=lowbit(i))
		bit[i]+=v;
}

inline ll sum(int pos)
{
	ll ans=0;
	for(int i=pos;i;i-=lowbit(i))
		ans+=bit[i];
	return ans;
}

ll gcd(ll a,ll b)
{
	return b==0?a:gcd(b,a%b);
}

inline void up(int i)
{
	tree[i].v=gcd(tree[i<<1].v,tree[i<<1|1].v);
}

void build(int i,int l,int r)
{
	tree[i].l=l,tree[i].r=r;
	if(l==r)
	{
		tree[i].v=dis[l];
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	up(i);
}

void update(int i,int pos,ll v)
{
	if(tree[i].l==tree[i].r)
	{
		tree[i].v+=v;
		return;
	}
	int mid=(tree[i].l+tree[i].r)>>1;
	if(pos<=mid)
		update(i<<1,pos,v);
	else
		update(i<<1|1,pos,v);
	up(i);
}

ll query(int i,int l,int r)
{
	if(tree[i].l==l&&tree[i].r==r)
		return tree[i].v;
	int mid=(tree[i].l+tree[i].r)>>1;
	if(r<=mid)
		return query(i<<1,l,r);
	else if(l>mid)
		return query(i<<1|1,l,r);
	else
		return gcd(query(i<<1,l,mid),query(i<<1|1,mid+1,r));
}

inline void prework()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]),dis[i]=a[i]-a[i-1];
	for(int i=1;i<=n;i++)
		add(i,dis[i]);
	build(1,1,n);
}

inline void mainwork()
{
	char op[10];
	int l,r;
	ll d;
	for(int i=0;i<m;i++)
	{
		scanf("%s%d%d",op,&l,&r);
		if(op[0]=='C')
		{
			scanf("%lld",&d);
			add(l,d);
			add(r+1,-d);
			update(1,l,d);
			if(r+1<=n)
				update(1,r+1,-d);
		}
		else
			printf("%lld\n",l==r?sum(l):gcd(abs(sum(l)),abs(query(1,l+1,r))));
	}
}

int main()
{
	prework();
	mainwork();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值