caioj 2056 & 0x40数据结构进阶(0x43 线段树)例题2:区间最大公约数

本文介绍了一种高效求解区间最大公约数(GCD)问题的方法,通过运用差分线段树和树状数组,实现了O(log²n)的时间复杂度。文中详细解释了更相减损术原理及其在多个数之间的应用,并提供了两种具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

此题细节很多

1. g c d ( a , b ) = g c d ( a , b − a ) gcd(a,b)=gcd(a,b−a) gcd(a,b)=gcd(a,ba)

在lyd的书里提到的这个性质叫更相减损术,可以推广到多个数的情况。

更相减损术其实是欧几里得算法的一个特例。即 g c d ( a − n b , b ) = g c d ( a , b ) gcd(a−nb,b)=gcd(a,b) gcd(anb,b)=gcd(a,b)

推广:

( a , b , c ) = ( ( a , b ) , ( b , c ) ) = ( ( a , b − a ) , ( b , c − b ) ) = ( a , b − a , b , c − b ) (a,b,c)=((a,b),(b,c))=((a,b−a),(b,c−b))=(a,b-a,b,c-b) (a,b,c)=((a,b),(b,c))=((a,ba),(b,cb))=(a,ba,b,cb)
由 于 ( b − a , b ) = ( a , b − a ) 所 以 ( a , b , c ) = ( a , b − a , c − b ) 由于(b−a,b)=(a,b−a)所以(a,b,c)=(a,b−a,c−b) (ba,b)=(a,ba)(a,b,c)=(a,ba,cb)

再推广一下:

( a , b , c , d ) = ( ( a , b , c ) , ( b , c , d ) ) = ( ( a , b − a , c − b ) , ( b , c − b , d − c ) ) = (a,b,c,d)=((a,b,c),(b,c,d))=((a,b-a,c-b),(b,c-b,d-c))= (a,b,c,d)=((a,b,c),(b,c,d))=((a,ba,cb),(b,cb,dc))=
( a , b − a , c − b , b , c − b , d − c ) = ( 去 重 , 化 简 ) ( a , b − a , c − b , d − c ) (a,b-a,c-b,b,c-b,d-c)=(去重,化简)(a,b-a,c-b,d-c) (a,ba,cb,b,cb,dc)=(,)(a,ba,cb,dc)

继续不耐烦地推广:

( a , b , c , d , e ) = ( ( a , b , c , d ) , ( b , c , d , e ) ) = ( ( a , b − a , c − b , d − c ) , ( b , c − b , d − c , e − d ) ) = (a,b,c,d,e)=((a,b,c,d),(b,c,d,e))=((a,b-a,c-b,d-c),(b,c-b,d-c,e-d))= (a,b,c,d,e)=((a,b,c,d),(b,c,d,e))=((a,ba,cb,dc),(b,cb,dc,ed))=
( 去 重 , 化 简 ) ( a , b − a , c − b , d − c , e − d ) (去重,化简)(a,b-a,c-b,d-c,e-d) (,)(a,ba,cb,dc,ed)

这个时候,我们可以使用数学归纳法大招得到:

g c d ( a 1 , a 2 , a 3 , … … a n ) = g c d ( a 1 , a 2 − a 1 , a 3 − a 2 … … , a n − a n − 1 ) gcd(a_1,a_2,a_3,……a_n)=gcd(a_1,a_2-a_1,a_3-a_2……,a_n-a_{n-1}) gcd(a1,a2,a3,an)=gcd(a1,a2a1,a3a2,anan1)
其 实 对 于 n 个 数 的 最 大 公 约 数 , 我 们 可 以 采 用 上 面 的 方 法 , 分 为 前 n − 1 个 数 和 后 n − 1 个 数 其实对于n个数的最大公约数,我们可以采用上面的方法,分为前n-1个数和后n-1个数 n,,n1n1
有了这个式子说明可以通过维护序列的差分同样可以求 g c d gcd gcd
利用差分就可以把区间加减变成双点加减。可以用没有 l a z y lazy lazy的线段树来维护差分值的 g c d gcd gcd
最后用树状数组维护差分,这样就可以 O ( l o g 2 n ) O(log_2n) O(log2n)求对应的值.
当我们求解 g c d ( a l , a l + 1 , … … a r ) gcd(a_l,a_{l+1},……a_r) gcd(al,al+1,ar)时,可转化为
g c d ( a l , g c d ( a l + 1 − a l , … … , a r − a r − 1 ) ) gcd(a_l,gcd(a_{l+1}-a_l,……,a_r-a_{r-1})) gcd(al,gcd(al+1al,,arar1)).

悄咪咪地提醒:这题要开 l o n g   l o n g long~long long long

下面提供两种建树不同的方法:(貌似方法1快但内存大)

方法1:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
typedef long long ll;
const int N=1<<19;
template<class o>
inline void qr(o&x) {
	char c=g;x=0;int f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=g;}
	while(isdigit(c))x=x*10+c-'0',c=g;
	x*=f;
}
void write(ll x) {
	if(x/10)write(x/10);
	putchar(x%10+'0');
}

ll a[N],c[N<<1];
ll gcd(ll a,ll b){return !a?b:gcd(b%a,a);}
void bt(int x,int l,int r) {
	if(l==r) { c[x]=a[l]-a[l-1]; return ;}
	int mid=(l+r)>>1;
	bt(lc,l,mid);
	bt(rc,mid+1,r);
	c[x]=gcd(c[lc],c[rc]);
}
void change(int x,int l,int r,const int &pos,const ll &d) {
	if(l==r) { c[x]+=d; return ;}
	int mid=(l+r)>>1;
	if(pos<=mid)change(lc,l,mid,pos,d);
	else 		change(rc,mid+1,r,pos,d);
	c[x]=gcd(c[lc],c[rc]);
}
ll ans;
void query(int x,int l,int r,const int &L,const int &R) {
	if(L<=l&&r<=R) 
		{ans=gcd(ans,c[x]);return;}
	int mid=(l+r)>>1;
	if(L<=mid) query(lc,l,mid,L,R);
	if(mid< R) query(rc,mid+1,r,L,R);
}
int n,m; ll b[N];
inline void add(int x,ll y) { for(	;x<=n;x+=x&-x)b[x]+=y; }
ll sum(int x) { ll y=0; for(	;x;x&=x-1)y+=b[x]; return y;}
int main() {
	qr(n);qr(m);
	for(int i=1;i<=n;i++)	
		qr(a[i]);
	bt(1,1,n);
	while(m--) {
		char s[4];int l,r;ll d;
		scanf("%s",s);qr(l);qr(r);
		switch(s[0]) {
			case 'C':
				qr(d);
				change(1,1,n,l,d);
				if(r<n) d=-d,change(1,1,n,r+1,d),d=-d;
				add(l,d);add(r+1,-d);
				break;
			case 'Q':
				ans=abs(sum(l)+a[l]);
				if(l<r)query(1,1,n,l+1,r);
				write(abs(ans));puts("");
				break;
		}
	}
	return 0;
}

方法2:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=5e5+10;
struct node{int l,r,lc,rc;ll c;}tr[N*2];int len;
int n,m;
ll a[N],b[N],c[N],d,ans;
ll gcd(ll a,ll b){return !a?b:gcd(b%a,a);}
void bt(int l,int r)
{
	int x=++len;
	tr[x].l=l;tr[x].r=r;
	if(l==r){tr[x].c=b[l];return;}
	int mid=(l+r)>>1;
	tr[x].lc=len+1;bt(l,mid);
	tr[x].rc=len+1;bt(mid+1,r);
	tr[x].c=gcd(tr[tr[x].lc].c,tr[tr[x].rc].c);
}
void change(int now,int x)
{
	if(tr[now].l==tr[now].r){tr[now].c+=d;return;}
	int mid=(tr[now].l+tr[now].r)>>1;
	if(x<=mid)change(tr[now].lc,x);
	else change(tr[now].rc,x);
	tr[now].c=gcd(tr[tr[now].lc].c,tr[tr[now].rc].c);
}
void ask(int now,int l,int r)
{
	if(l<=tr[now].l&&tr[now].r<=r){ans=gcd(ans,tr[now].c);return;}
	int mid=(tr[now].l+tr[now].r)>>1;
	if(l<=mid)ask(tr[now].lc,l,r);
	if(mid<r)ask(tr[now].rc,l,r);
}
ll sum(int x)
{
	ll y=0;
	for(   ; x ; x-= x & -x)y+=c[x];
	return y;
}
ll add(int x,ll y)
{
	for(   ;x<=n;x+= x & - x)c[x]+=y;
}
template<class o>
void qr(o&x)
{
	char c=g;bool v=(x=0);
	while(!( ('0'<=c&&c<='9') || c=='-' ))c=g;
	if(c=='-')v=1,c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
	if(v)x=-x;
}
void write(ll x)
{
	if(x/10)write(x/10);
	putchar(x%10+'0');
}
int main()
{
	freopen("2056.in","r",stdin);
	freopen("2056.out","w",stdout);
	qr(n);qr(m);
	for(int i=1;i<=n;i++)qr(a[i]),b[i]=a[i]-a[i-1];
	bt(1,n);
	while(m--)
	{
		char s[2];int l,r;
		scanf("%s",s);qr(l);qr(r);
		switch(s[0]){
			case 'C':
				qr(d);
				change(1,l);
				if(r<n)d=-d,change(1,r+1),d=-d;
				add(l,d);add(r+1,-d);
				break;
			case 'Q':
				ans=0;if(l<r)ask(1,l+1,r);
				ll al=a[l]+sum(l);
				write(abs(gcd(al,ans)));puts("");
				break;
			}
	}
	return 0;
}
				
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值