题意:
维护一个数列,支持:
1、区间内所有的数变为其约数个数。
2、区间求和。
(1<=n,m<=3e5,1<=ai<=1e6)
分析:
首先我们发现,一个数变为其约数个数最悲观也会减小为原来的一半左右,所以一个数最多只会改变log1e6=16次就会变为1或2。
这里我们用到的均摊复杂度的思想可参考BZOJ3211或GSS3。
因此,我们对这个数列建一棵线段树,维护区间sum和max。
修改时要递归到叶节点,但若一个区间的max<=2那么直接跳过(因为D(1)=1,D(2)=2)。
这样就实现了均摊复杂度。
约数个数函数可线性筛预处理筛出来即可(不懂的看代码或自行百度约数个数定理)。
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch==-'-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=300005;
const int MAXNUM=1000005;
int n,d[MAXNUM],cnt[MAXNUM],prm[MAXNUM],tot;
bool vis[MAXNUM];
int a[MAXN],maxn[MAXN<<2],ql,qr;
LL sum[MAXN<<2];
void pre_process(){
d[1]=1,cnt[1]=0;
for(int i=2;i<=n;i++){
if(!vis[i]){
prm[++tot]=i;
d[i]=2;
cnt[i]=1;
}
for(int j=1;j<=tot&&prm[j]<=n/i;j++){
vis[i*prm[j]]=1;
if(i%prm[j]==0){
cnt[i*prm[j]]=cnt[i]+1;
d[i*prm[j]]=d[i]/(cnt[i]+1)*(cnt[i*prm[j]]+1);
break;
}
cnt[i*prm[j]]=1;
d[i*prm[j]]=d[i]*2;
}
}
}
#define mid ((l+r)>>1)
#define lc (o<<1)
#define rc ((o<<1)|1)
void build(int o,int l,int r){
if(l==r){
sum[o]=maxn[o]=a[l];
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
maxn[o]=max(maxn[lc],maxn[rc]);
sum[o]=sum[lc]+sum[rc];
}
void upd(int o,int l,int r){
if(maxn[o]<=2) return;
if(l==r){
sum[o]=maxn[o]=d[maxn[o]];
return;
}
if(mid>=ql) upd(lc,l,mid);
if(mid<qr) upd(rc,mid+1,r);
maxn[o]=max(maxn[lc],maxn[rc]);
sum[o]=sum[lc]+sum[rc];
}
LL query(int o,int l,int r){
if(ql<=l&&r<=qr) return sum[o];
LL ans=0;
if(mid>=ql) ans+=query(lc,l,mid);
if(mid<qr) ans+=query(rc,mid+1,r);
return ans;
}
int main(){
n=1000005;
pre_process();
n=read();int q=read();
for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
while(q--){
int opt=read();
if(opt==1){
ql=read(),qr=read();
upd(1,1,n);
}
else{
ql=read(),qr=read();
printf("%lld\n",query(1,1,n));
}
}
return 0;
}