计蒜客 QUERY(主席树)

题目链接

思路及代码来自: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);
	}
	
 } 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值