P4256 题解
最近对线段树有点着迷
题意简述
给出序列 $\{a_1,a_2,\cdots,a_n\}(n\le 3\times10^5,1\le a\le100)$,有 $q(1\le q\le 3\times 10^5)$ 次操作,共 $4$ 种:
-
查询 $a_l,a_{l+1},\cdots,a_r$ 的最小公倍数 $\mod x$ 的值。
-
查询 $a_l,a_{l+1},\cdots,a_r$ 的最大公约数 $\mod x$ 的值。
-
查询 $a_l,a_{l+1},\cdots,a_r$ 的公约数个数 $\mod x$ 的值。
-
将 $a_l,a_{l+1},\cdots,a_r$ 更改为 $x(1\le x\le 100)$。
注:模数 $x$ 不固定。
题目分析
乍一看,维护最小公倍数,模数还在变,什么毒瘤题……
但是!注意到 $a_i$ 始终在 $[1,100]$ 之间,也就是说它们的质因数个数有限,$[1,100]$ 内的质数更是只有 $25$ 个。
我们考虑使用线段树的每个结点维护 $a_l,a_{l+1},\cdots,a_r$ 的最大公约数及最小公倍数的质因数分解中,每个质因数及其次数。那么该如何合并呢?很简单。每个结点的最小公倍数也就是其左右子结点的最小公倍数的最小公倍数,将左右子结点的次数取一个 $\max$ 即可;最大公约数类似,取一个 $\min$ 即可。
至于查询,$1$ 类查询和 $2$ 类查询就是线段树板子,直接按照结果对应的每个质数的次数乘起来就好了。对于 $3$ 类查询,实质上就是求最大公约数的约数个数,实现比较简单。
另外要特判模数为 $0$ 的情况,不然会寄。
其他大佬都用的压位,但是好像直接记录次数也不会慢特别多,空间也还行
代码实现
#include<bits/stdc++.h>
using namespace std;
const int P[25]={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 n,q,a[300010],l,r,x,ans;
char op[2];
struct sep
{
int a[25];
void init()
{
memset(a,0,sizeof a);
}
}tmp;//一个数质因数分解后,每个质数的次数。为了方便就封装了一个结构体。
struct node
{
int l,r,tag;
sep mn,mx;
}tr[1200010];//线段树结点
sep max(sep a,sep b)
{
for(int i=0;i<25;i++)
a.a[i]=max(a.a[i],b.a[i]);
return a;
}
sep min(sep a,sep b)
{
for(int i=0;i<25;i++)
a.a[i]=min(a.a[i],b.a[i]);
return a;
}
void pushup(int p)
{
tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx);
tr[p].mn=min(tr[p<<1].mn,tr[p<<1|1].mn);
}//更新结点
void addtag(int p,int val)
{
tr[p].tag=val;
tr[p].mn.init(),tr[p].mx.init();
for(int i=0;i<25&&val>1;i++)
while(val%P[i]==0)
{
tr[p].mn.a[i]++;
val/=P[i];
}
tr[p].mx=tr[p].mn;
}//加标记
void pushdown(int p)
{
if(tr[p].tag)
{
addtag(p<<1,tr[p].tag);
addtag(p<<1|1,tr[p].tag);
tr[p].tag=0;
}
}//懒标记下传
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].tag=0;
if(l==r)
{
addtag(p,a[l]);
tr[p].tag=0;
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}//建树
void change(int p,int l,int r,int val)
{
if(tr[p].l>=l&&tr[p].r<=r)
{
addtag(p,val);
return;
}
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid>=l)
change(p<<1,l,r,val);
if(mid<r)
change(p<<1|1,l,r,val);
pushup(p);
}//修改
sep query1(int p,int l,int r)
{
if(tr[p].l>=l&&tr[p].r<=r)
return tr[p].mx;
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid<l)
return query1(p<<1|1,l,r);
if(mid>=r)
return query1(p<<1,l,r);
return max(query1(p<<1,l,r),query1(p<<1|1,l,r));
}//查询最小公倍数
sep query2(int p,int l,int r)
{
if(tr[p].l>=l&&tr[p].r<=r)
return tr[p].mn;
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid<l)
return query2(p<<1|1,l,r);
if(mid>=r)
return query2(p<<1,l,r);
return min(query2(p<<1,l,r),query2(p<<1|1,l,r));
}//查询最大公约数
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
while(q--)
{
scanf("%s%d%d%d",op,&l,&r,&x);
if(op[0]=='L')
{
ans=1;
tmp=query1(1,l,r);
for(int i=0;i<25;i++)
for(int j=1;j<=tmp.a[i];j++)
ans=1ll*ans*P[i]%x;//根据每个质数的次数乘回去
printf("%d\n",ans%x);//一定要 %x !!否则 x=1 时会寄
}//最小公倍数
else if(op[0]=='G')
{
ans=1;
tmp=query2(1,l,r);
for(int i=0;i<25;i++)
for(int j=1;j<=tmp.a[i];j++)
ans=1ll*ans*P[i]%x;
printf("%d\n",ans%x);
}//最大公约数
else if(op[0]=='C')
change(1,l,r,x);//修改
else
{
ans=1;
tmp=query2(1,l,r);
for(int i=0;i<25;i++)
ans=1ll*ans*(tmp.a[i]+1)%x;
printf("%d\n",ans%x);
}//公约数的个数实际上就是最大公约数的约数个数
}
return 0;
}