P5398 [Ynoi2018] GOSICK 题解

第十四分块的题目,我们考虑莫队二次离线。

题目中要求出满足\(l \le x,y \le r\)\(a_l | a_r\)的个数。我们考虑定义函数\(f(a,b,c)\)表示满足\(x=a,b \le y \le c\)的答案的个数,那么一次询问就是求\(\sum_{i=l}^{i \le r} f(i,l,r)\)我们进行一个转换,有一个显然的事实,我们找出\(x < y < z\)那么对于任意\(i\)均有\(f(i,y,z)=f(i,x,z)-f(i,x,y-1)\)这一事实,所以我们可以把问题转化为\(\sum_{i=1}^{i\le r}f(i,l,r)-\sum_{i=1}^{i\le l-1}f(i,l,r)\)这就是莫队二次离线。

我们考虑普通莫队,那么我们会移动\(O(n\sqrt n)\)次,运用上述柿子进行计算,我们每一次移动相当于求出一个点对一个前缀的答案,然后对于\(a_i\)的因数在\(5e5\)内可以看作\(O(\sqrt n)\),对于\(a_i\)的倍数,在\(a_i \le 100\)的时候每一次都可以遍历整个序列离线快速处理出来倍数的个数,对于\(a_i > 100\)的时候可以开一个桶解决这个问题。(关于为什么要用\(100\),不用\(\sqrt n\),这是因为后半部分常数显然大于前半部分,用小的闸值会让你跑得更快。)

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=500010;
const int V=500000;
int tp,n,m,B,lim,cc[maxn],a[maxn],b1[maxn],b2[maxn],cnt1[maxn],cnt2[maxn];
long long f1[maxn],f2[maxn],ans[maxn],lin,ci;
vector<int>F[maxn];
struct edge{
	int l;
	int r;
	int id;
	int b;
	long long sum;
}xun[maxn];
struct node{
	int l;
	int r;
	int p;
	int id;
}Q[maxn<<1];
int cmp(edge q,edge w){
	if(q.b!=w.b){
		return q.b<w.b;
	}
	if(q.b&1){
		return q.r<w.r;
	}
	return q.r>w.r;
}
int cp(node q,node w){
	return q.p<w.p;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	B=sqrt(n);
	lim=100;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		if(!F[a[i]].size()){
			for(int j=1;j*j<=a[i];j++){
				if(a[i]%j==0){
					F[a[i]].push_back(j);
					b1[j]++,f1[i]+=cc[j];
					if(j*j!=a[i]){
						b1[a[i]/j]++;
						f1[i]+=cc[a[i]/j];
					}
				}
			}
		}
		else{
			for(int j=0;j<F[a[i]].size();j++){
				b1[F[a[i]][j]]++;
				f1[i]+=cc[F[a[i]][j]];
				if(F[a[i]][j]*F[a[i]][j]!=a[i]){
					b1[a[i]/F[a[i]][j]]++;
					f1[i]+=cc[a[i]/F[a[i]][j]];
				}
			}
		}
		f1[i]+=f1[i-1]+b1[a[i]];
		cc[a[i]]++;
	}//预分解因数 
	for(int i=1;i<=m;i++){
		cin>>xun[i].l>>xun[i].r;
		xun[i].sum=0;
		xun[i].id=i;
		xun[i].b=(xun[i].l-1)/B+1;
	}
	sort(xun+1,xun+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		if(r<xun[i].r){
			xun[i].sum+=f1[xun[i].r]-f1[r];
			Q[++tp]=(node){r+1,xun[i].r,l-1,-i};
			r=xun[i].r;
		}
		if(r>xun[i].r){
			xun[i].sum-=f1[r]-f1[xun[i].r];
			Q[++tp]=(node){xun[i].r+1,r,l-1,i};
			r=xun[i].r;
		}
		if(l>xun[i].l){
			xun[i].sum-=f1[l-1]-f1[xun[i].l-1];
			Q[++tp]=(node){xun[i].l,l-1,r,i};
			l=xun[i].l;
		}
		if(l<xun[i].l){
			xun[i].sum+=f1[xun[i].l-1]-f1[l-1];
			Q[++tp]=(node){l,xun[i].l-1,r,-i};
			l=xun[i].l;
		}
	}//一次存一段对一个点,不然空间开不下。 
	sort(Q+1,Q+1+tp,cp);
	int p=0;
	for(int i=1;i<=tp;i++){
		while(p<Q[i].p){
			p++;
			for(int j=0;j<F[a[p]].size();j++){
				b2[F[a[p]][j]]++;
				if(F[a[p]][j]*F[a[p]][j]!=a[p]){
					b2[a[p]/F[a[p]][j]]++;
				}
			}//因数 
			if(a[p]>lim){
				for(int j=1;j*a[p]<=V;j++){
					b2[j*a[p]]++;
				}
			}//大于100的倍数 
		}
		for(int j=Q[i].l;j<=Q[i].r;j++){
			if(Q[i].id<0){
				xun[-Q[i].id].sum-=b2[a[j]];
			}
			else{
				xun[Q[i].id].sum+=b2[a[j]];
			}
		}
		ci=ci+Q[i].r-Q[i].l+1;
	}
	for(int j=1;j<=lim;j++){
		cnt1[0]=0;
		cnt2[0]=0;
		for(int i=1;i<=n;i++){
			cnt1[i]=cnt1[i-1]+(a[i]==j);
			cnt2[i]=cnt2[i-1]+(a[i]%j==0);
		}
		for(int i=1;i<=tp;i++){
			lin=cnt1[Q[i].p]*(cnt2[Q[i].r]-cnt2[Q[i].l-1]);
			if(Q[i].id<0){
				xun[-Q[i].id].sum-=lin;
			}
			else{
				xun[Q[i].id].sum+=lin;
			}
		}
	}//小于100的倍数 
	for(int i=1;i<=m;i++){
		xun[i].sum+=xun[i-1].sum;
		ans[xun[i].id]=xun[i].sum;
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
posted @ 2025-04-20 17:42  特别之处  阅读(3)  评论(0)    收藏  举报