CH 4302 Intervavl(线段树维护区间gcd,区间加)

传送门

描述
给定一个长度为N的数列A,以及M条指令 (N≤510^5, M<=10^5),每条指令可能是以下两种之一:
“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
“Q l r”,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
输入格式
第一行两个整数N,M,第二行N个整数Ai,接下来M行每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
样例输入
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
数据范围与约定
N,M≤2
10^5, l<=r,数据保证任何时刻序列中的数都是不超过2^62-1的正整数。

不要在意数据不统一的事

区间 g c d gcd gcd好做

难点在于如何维护区间加

考虑辗转相减法:
g c d ( x , y ) = g c d ( x , x − y ) gcd(x,y)=gcd(x,x-y) gcd(x,y)=gcd(x,xy)

我们可以将这个东西拓展到三个数:
g c d ( x , y , z ) = g c d ( x , y − x , z − y ) gcd(x,y,z)=gcd(x,y-x,z-y) gcd(x,y,z)=gcd(x,yx,zy)

证明(摘自 g s j gsj gsj神仙的 b l o g blog blog):

g c d ( x , y , z ) = a gcd(x,y,z)=a gcd(x,y,z)=a,那么必然存在互质的三个数 k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3使 x = k 1 a , y = k 2 a , z = k 3 a x=k_1a,y=k_2a,z=k_3a x=k1a,y=k2a,z=k3a
那么 g c d ( x , y − x , z − y ) = g c d ( k 1 a , ( k 2 − k 1 ) a , ( k 3 − k 2 ) a ) gcd(x,y-x,z-y)=gcd(k_1a,(k_2-k_1)a,(k_3-k_2)a) gcd(x,yx,zy)=gcd(k1a,(k2k1)a,(k3k2)a)
由于 k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3互质,则 k 1 , k 2 − k 1 , k 3 − k 2 k_1,k_2-k_1,k_3-k_2 k1,k2k1,k3k2都必然互质

那么我们要求的区间 g c d gcd gcd就可以变成差分数组 g c d ( a [ l ] , d e l [ l + 1 ] gcd(a[l],del[l+1] gcd(a[l],del[l+1],…, d e l [ r ] ) del[r]) del[r])
我们发现这时候区间加其实只对应了 l , r + 1 l,r+1 l,r+1的单点加减

那我们可以先线段树维护区间差分数组的 g c d gcd gcd

再单独维护一个单点值

最后合起来计算一下 g c d gcd gcd就可以了

复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
	char ch=getchar();
	int res=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch))res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
	return res*f;
}
const int N=5000005;
int  tr[N<<2],bit[N],a[N],n,m;
inline int lowbit(int x){
	return (x&(-x));
}
inline void update(int pos,int k){
	for(;pos<=n;pos+=lowbit(pos))bit[pos]+=k;
}
inline int bitsum(int pos,int res=0){
	for(;pos;pos-=lowbit(pos))res+=bit[pos];return res;
}
int gcd(int x,int y){
	return y==0?x:gcd(y,x%y);
}
#define lc (u<<1)
#define rc ((u<<1)|1)
#define mid ((l+r)>>1)
inline void pushup(int u){
	tr[u]=gcd(tr[lc],tr[rc]);
}
void buildtree(int u,int l,int r){
	if(l==r){
		tr[u]=a[l]-a[l-1];return;
	}
	buildtree(lc,l,mid);
	buildtree(rc,mid+1,r);
	pushup(u);
}
void add(int u,int l,int r,int pos,int k){
	if(l==r){
		tr[u]+=k;return;
	}
	if(mid<pos)add(rc,mid+1,r,pos,k);
	else add(lc,l,mid,pos,k);
	pushup(u);
}
int query(int u,int l,int r,int st,int des){
	if(st<=l&&r<=des){
		return tr[u];
	}
	if(des<=mid)return query(lc,l,mid,st,des);
	if(mid<st)return query(rc,mid+1,r,st,des);
	return gcd(query(lc,l,mid,st,des),query(rc,mid+1,r,st,des));
}
signed main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	buildtree(1,1,n);
	for(int i=1;i<=m;i++){
		char op[4];
		scanf("%s",op);
		switch(op[0]){
			case 'Q':{
				int l=read(),r=read();
				cout<<abs(gcd(bitsum(l)+a[l],query(1,1,n,l+1,r)))<<'\n';
				break;
			}
			case 'C':{
				int l=read(),r=read(),v=read();
				add(1,1,n,l,v);if(r<n)add(1,1,n,r+1,-v);
				update(l,v);if(r<n)update(r+1,-v);
				break;
			}
		}
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值