(HDU3071)Gcd & Lcm game(线段树+质因子状压)

题目链接:Problem - 3071 (hdu.edu.cn)

这道题目是有关两个数与其公约数和公倍数之间的关系的,我在这先介绍一下他们之间的关系:

对于两个数a和b,有质因子分解定理可得:

a = ( p1^x1 ) * ( p2^x2 ) * ( p3^x3 ) * …… * ( pn^xn )

b = ( p1^y1 ) * ( p2^y2 ) * ( p3^y3 ) * …… * ( pn^yn )

则 lcm( a , b ) = ( p1^max (x1 , y1) ) * ( p2^max (x2 , y2) ) * ( p3^ max (x3 , y3) * …… * pn^max (xn , yn) )

而gcd( a, b ) = ( p1^min (x1 , y1) ) * ( p2^min (x2 , y2) ) * ( p3^ min (x3 , y3) * …… * pn^min (xn , yn) )
对于这道题目的最大公约数,我们可以用线段树维护素因子出现的最小次数,而对于最小公倍数则是要维护素因子出现的最大次数,在之前的博客中我介绍过一道与这种题目非常类似的题目,这个算是那道题目的升级版吧!在这我给出博客地址:(21条消息) 强制在线带修区间LCM(线段树+质因子状压)_AC__dream的博客-CSDN博客

之前的博客讲的比较详细,这道题我在这就不多做说明了,下面给出一些容易错误的地方。

(1)一定不要全开longlong,这样的时间花销要比int多的多,会TLE,只需在取模时开longlong就好

(2)求最大公约数的时候我们要求素因子出现最少的次数,所以初始值应设为每个素因子出现的次数最多,而求最小公倍数时则恰恰相反

下面是代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e5+10;
int sumlcm[N],sumgcd[N],l[N],r[N],a[N],mod;
int prime[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
int Move[]={28,25,23,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};
int ppow(long long a,long long b)//快速幂
{
	long long ans=1;//快速幂过程中会爆int 
	while(b)
	{
		if(b&1) ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}
int lcm(int x,int y)//求出每个质数的最大出现次数
{
    return max(x&0x70000000,y&0x70000000)|max(x&0x0e000000,y&0x0e000000)|max(x&0x01800000,y&0x01800000)|max(x&0x00600000,y&0x00600000)|( (x|y)&0x001fffff );
}
int gcd(int x,int y)//求出每个质数的最小出现次数
{
    return min(x&0x70000000,y&0x70000000)|min(x&0x0e000000,y&0x0e000000)|min(x&0x01800000,y&0x01800000)|min(x&0x00600000,y&0x00600000)|( (x&y)&0x001fffff );
}
int change(int x)//把x的质因子压缩成一个数
{
	int ans=0;
	if(x==0)
	return 0;
	
	for(int i=0;i<25;i++)
	{
		int cnt=0;
		while(x%prime[i]==0)
		{
			x/=prime[i];
			cnt++;
		}
		ans+=cnt<<Move[i];
	}
	
	return ans;
}
int relive(int x)//把压缩后的数展开 
{
	int ans=1;
	for(int i=0;i<25;i++)
	{
		int t=x>>Move[i];//记录prime[i]作为质因子出现的次数
		ans=ans*ppow(prime[i],t)%mod;
		x^=(t<<Move[i]);//将高位用过后置0
	}
	return ans;
}
void pushup(int id)
{
	sumlcm[id]=lcm(sumlcm[id<<1],sumlcm[id<<1|1]);
	sumgcd[id]=gcd(sumgcd[id<<1],sumgcd[id<<1|1]);
}
void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;sumlcm[id]=0;sumgcd[id]=0xffffffff;//注意赋初值的不同 
	if(L==R)
	{
		sumlcm[id]=sumgcd[id]=change(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])
	{
		sumlcm[id]=sumgcd[id]=change(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);
}
int lcmquery_interval(int id,int L,int R)
{
	if(l[id]>R||r[id]<L) return 0;
	if(l[id]>=L&&r[id]<=R) return sumlcm[id];
	return (lcm(lcmquery_interval(id<<1,L,R),lcmquery_interval(id<<1|1,L,R)));
}
int gcdquery_interval(int id,int L,int R)
{
	if(l[id]>R||r[id]<L) return 0xffffffff;//注意求最大公约数时应返回全1,保证求素因子最小值时不受影响 
	if(l[id]>=L&&r[id]<=R) return sumgcd[id];
	return (gcd(gcdquery_interval(id<<1,L,R),gcdquery_interval(id<<1|1,L,R)));
}
signed main()
{
	int n,q;
	while(~scanf("%d%d",&n,&q))
	{
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		build(1,1,n);
		char op[2];
		int l,r;
		while(q--)
		{
			scanf("%s",op);
			if(op[0]=='L')
			{
				scanf("%d%d%d",&l,&r,&mod);
				printf("%d\n",relive(lcmquery_interval(1,l,r)));
			}
			else if(op[0]=='G')
			{
				scanf("%d%d%d",&l,&r,&mod);
				printf("%d\n",relive(gcdquery_interval(1,l,r)));
			}
			else
			{
				scanf("%d%d",&l,&r);
				update_point(1,l,r);
			}
		}
	}
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值