题目链接: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;
}