思路及代码来自:ccsu_cat
题意:给你一个序列,有q次询问,问区间 l 到 r 中间有多少对数,满足min(x,y)=gcd(x,y)。
思路:我们对每个数建一棵可持续化权值线段树,保存这个数前面的因数及倍数的个数(包括它在内的合法答案的个数)。因为,主席树保存了区间的前缀信息,那么我们要查询的答案就是第r棵树的l到r的值减去第l-1颗线段树的值。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int a[maxn];
int ls[maxn*190],rs[maxn*190],rt[maxn],sum[maxn*190],cnt;
vector<int>v[maxn];
void up(int& k,int pre,int l,int r,int p)
{
k=++cnt;
ls[k]=ls[pre];
rs[k]=rs[pre];
sum[k]=sum[pre]+1;
if(l==r)return ;
int m=(l+r)/2;
if(p<=m)up(ls[k],ls[pre],l,m,p);
else up(rs[k],rs[pre],m+1,r,p);
}
int qu(int k,int l,int r,int L,int R)
{
if(l>=L&&r<=R)return sum[k];
int ans=0;
int m=(l+r)/2;
if(m>=L)ans+=qu(ls[k],l,m,L,R);
if(m<R)ans+=qu(rs[k],m+1,r,L,R);
return ans;
}
int main()
{
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++)
{
int p;
cin>>p;
a[p]=i;
}
for(int i=1;i<=n;i++)
{
for(int j=i*2;j<=n;j+=i)//调和级数求和是logn
{
if(a[i]>a[j])
{
v[a[i]].push_back(a[j]);
}
else if(a[i]<a[j])
{
v[a[j]].push_back(a[i]);
}
}
}
for(int i=1;i<=n;i++)
{
rt[i]=rt[i-1];
for(int j=0;j<v[i].size();j++)
{
up(rt[i],rt[i],1,n,v[i][j]);
}
}
while(q--)
{
int l,r;
scanf("%d%d",&l,&r);
int ans=qu(rt[r],1,n,l,r)-qu(rt[l-1],1,n,l,r);
printf("%d\n",ans);
}
}