洛谷 P5071 - [Ynoi2015] 此时此刻的光辉(莫队)

洛谷题面传送门

一道其实算得上常规的题,写这篇题解是为了总结一些数论中轻微(?)优化复杂度的技巧。

首先感性理解可以发现该问题强于区间数颜色问题,无法用常用的 log 数据结构维护,因此考虑分块/莫队。显然这题莫队比较好些对吧?显然我们要对每个质因子计算一遍它在 \([l,r]\) 中的出现次数对吧?涉及质因子就要分解质因数对吧?莫队时候新添一个元素很明显就要枚举它的每个质因子,然后计算新添的贡献对吧?线性预处理乘法逆元以后,复杂度就变成了 \(n\sqrt{a_i}+(n+q)\sqrt{n}\omega(a_i)\),其中左边的分解质因数的复杂度,右边是莫队的复杂度,对吧?恭喜你,你完美地获得了 TLE 的好成绩。

考虑优化。加号左右两部分显然都不可行,都要进行优化。左边一部分相对比较容易:考虑预处理出 \([1,\sqrt{a_i}]\) 中所有质因子,这样每次分解不必 \([1,\sqrt{a_i}]\) 跑一遍,只用跑其中 \(\mathcal O(\dfrac{\sqrt{a_i}}{\ln\sqrt{a_i}})\) 个质因子即可,这样左边的复杂度就变成了 \(\dfrac{\sqrt{a_i}}{\ln\sqrt{a_i}}·n\)。再考虑右边,注意到在每个数所有不同质因子中,大部分都比较小,因此考虑用类似于根号分治的方法,我们预处理 \([1,\sqrt[3]{a_i}]\) 中所有质因子,对于 \([1,\sqrt[3]{a_i}]\) 中质因子的贡献,我们直接前缀和扫一遍即可计算,复杂度 \((n+q)\pi(\sqrt[3]{a_i})\),对于 \(>\sqrt[3]{a_i}\) 的质因子的贡献就按照上面的方法进行莫队。注意到一个数最多有 \(2\) 个 \(>\sqrt[3]{a_i}\) 的因子,因此莫队的 \(\omega\) 就变成了常数,总复杂度也进而变为 \(\dfrac{\sqrt{a_i}}{\ln\sqrt{a_i}}·n+(n+q)\pi(\sqrt[3]{a_i})+(n+q)\sqrt{n}\)。

const int MAXN=1e5;
const int MAXV=31630;
const int LIM=1000;
const int PI_LIM=220;
const int MAXX=1e6;
const int BLK=316;
int n,qu,a[MAXN+5];
int pr[MAXV/6+5],prcnt=0,vis[MAXV+5];
void sieve(int n){
	for(int i=2;i<=n;i++){
		if(!vis[i]) pr[++prcnt]=i;
		for(int j=1;j<=prcnt&&pr[j]*i<=n;j++){
			vis[pr[j]*i]=1;if(i%pr[j]==0) break;
		}
	}
}
int c[MAXN+5][3],cc[MAXN+5];
int sum[MAXN+5][PI_LIM+5],inv[MAXX+5];
int res[MAXN+5],bel[MAXN+5],L[BLK+5],R[BLK+5],blk_sz,blk_cnt;
int key[MAXN*2+5],uni[MAXN*2+5],ccnt=0,num=0;
struct qry{
	int l,r,id;
	bool operator <(const qry &rhs){
		if(bel[l]^bel[rhs.l]) return bel[l]<bel[rhs.l];
		else if(bel[l]&1) return r<rhs.r;
		else return r>rhs.r;
	}
} q[MAXN+5];
int cnt[MAXN*2+5],mul=1;
void ins(int x){
	for(int i=1;i<=cc[x];i++){
		mul=1ll*mul*inv[cnt[c[x][i]]+1]%MOD;
		cnt[c[x][i]]++;
		mul=1ll*mul*(cnt[c[x][i]]+1)%MOD;
	}
}
void del(int x){
	for(int i=1;i<=cc[x];i++){
		mul=1ll*mul*inv[cnt[c[x][i]]+1]%MOD;
		cnt[c[x][i]]--;
		mul=1ll*mul*(cnt[c[x][i]]+1)%MOD;
	}
}
int main(){
	scanf("%d%d",&n,&qu);sieve(MAXV);
	blk_sz=(int)sqrt(n);blk_cnt=(n-1)/blk_sz+1;
	for(int i=1;i<=blk_cnt;i++){
		L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,n);
		for(int j=L[i];j<=R[i];j++) bel[j]=i;
	}
	for(int i=(inv[0]=inv[1]=1)+1;i<=MAXX;i++) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		int tmp=a[i];
		for(int j=1;j<=prcnt;j++) if(tmp%pr[j]==0){
			int cnt=0;
			while(tmp%pr[j]==0) tmp/=pr[j],cnt++;
			if(pr[j]<=LIM) sum[i][j]=cnt;
			else{
				c[i][++cc[i]]=pr[j];
				if(cnt==2) c[i][++cc[i]]=pr[j];
			}
		} if(tmp>1) c[i][++cc[i]]=tmp;
		for(int j=1;j<=cc[i];j++) key[++ccnt]=c[i][j];
	} sort(key+1,key+ccnt+1);
	for(int i=1;i<=n;i++) for(int j=1;j<=prcnt;j++){
		if(pr[j]>LIM) break;sum[i][j]+=sum[i-1][j];
	}
	for(int i=1;i<=ccnt;i++) if(key[i]^key[i-1]) uni[++num]=key[i];
	for(int i=1;i<=n;i++) for(int j=1;j<=cc[i];j++)
		c[i][j]=lower_bound(uni+1,uni+num+1,c[i][j])-uni;
	for(int i=1;i<=qu;i++){
		scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;res[i]=1;
		for(int j=1;j<=prcnt;j++){
			if(pr[j]>LIM) break;
			res[i]=1ll*res[i]*(sum[q[i].r][j]-sum[q[i].l-1][j]+1)%MOD;
		}
	} sort(q+1,q+qu+1);int cl=1,cr=0;
	for(int i=1;i<=qu;i++){
		while(cr<q[i].r) ins(++cr);
		while(cl>q[i].l) ins(--cl);
		while(cl<q[i].l) del(cl++);
		while(cr>q[i].r) del(cr--);
		res[q[i].id]=1ll*res[q[i].id]*mul%MOD;
	}
	for(int i=1;i<=qu;i++) printf("%d\n",res[i]);
	return 0;
}
上一篇:关于二次分块这么一个奇奇怪怪的东西


下一篇:菜鸟编程练习生之<素数的输出>循环与条件语句