hdu4777 Rabbit Kingdom(思维题+树状数组)

本文介绍了一种解决区间互质数查询问题的算法,通过预处理素因子、使用树状数组和离线处理查询来高效解答。文章详细解释了算法步骤,包括处理每个数的贡献区间、更新树状数组以及查询区间内符合条件的数的数量。

题目

给长度为n的数列a[],以下m个询问,

每个询问给出[li,ri],

问[li,ri]内与其他数都互质的数的个数

n,m,ai<=2e5

思路来源

https://www.cnblogs.com/alihenaixiao/p/4917359.html

题解

考虑离线做这个题,

先对所有的查询进行排序 (按照L升序排列) ,然后从左到右依次处理。

 

先处理出每个数有贡献的区间,

即这个数 [左边最近不互质数的位置,右边最近不互质数的位置] ,

处理这个,先预处理所有素因子,然后每个数内放入对应的素因子

l[i]代表i的左边最近不互质数的位置,r[i]代表i的右边最近不互质数的位置

先赋l[i]==0,r[i]==n+1的初始化,

递增遍历更新每个因子最新出现的位置,用其中最大的更新l[i]

递减遍历更新每个因子最新出现的位置,用其中最小的更新r[i]

 

现在问题就转化为了求区间 [L, R] 中有几个数的贡献区间能完整覆盖 [L, R] 区间

即L<=i<=R,且l[i]<L,r[i]>R

我们用树状数组区间和sum [L, R] 表示区间[L, R]中符合题意的个数。

假设现在需要查询 [L, R] 区间,我们可以考虑贡献区间L[i]<L的数,可以在i位置加 1,然后在R[i]位置减1。

这样处理的话必须要保证L[i]<L的值,以及i要在查询区间内,

这样如果R[i]<=R就抵消了i的贡献,R[i]>R说明i的区间完整包含[L,R],对答案造成1的贡献

 

我们每次向后移动的时候,要在树状数组中消除当前位置的数对树状数组的影响,

因为i这个值不可能包含在以i+1为左端点的区间中了,

也就是进行操作 i 位置减1,R[i]位置加1。

 

一些小细节操作简化代码的,

比如统一l[i]==0的操作,r[i]==n+1的时候也直接加不用特判

毕竟最后询问的时候答案是不会触碰到i==0和i==n+1的

 

心得

玄学卡常题还是挺毒瘤的

很考验代码功底的一道区域赛的题目

估计这题应该用线段树搞不了,很容易MLE的...

fac[]内得放素因子,直接放因子会MLE

网上的代码很多是MLE的思路来源也不例外

但的确博主的讲解很清晰让我及时补上了这道题

代码

#include<iostream>
#include<cstring>
#include<cmath> 
#include<algorithm> 
#include<vector> 
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int n,m,tree[maxn],a[maxn];
vector<int>fac[maxn];//fac[i]放入i的素因子 
vector<int>to[maxn];//to[L[i]]内放入i 
int l[maxn],r[maxn];//第i个数向左和向右第一个不互质的数的位置 
int pos[maxn],ans[maxn];
int prime[maxn],cnt; 
bool ok[maxn];
struct node
{
	int l,r,id;
}q[maxn];
bool cmp(node a,node b)
{
	return a.l<b.l;
}
//因子太多 预处理因子会MLE 
void init()//预处理每个数的素因子 
{
	for(ll i=2;i<maxn;++i)
	{
		if(!ok[i])prime[cnt++]=i;
		for(int j=0;j<cnt;++j)
		{
			ll k=prime[j]*i;
			if(k>maxn)break;
			ok[k]=1;
			if(i%prime[j]==0)break;
		}
	}
	for(int i=0;i<cnt;++i)
	{
		for(int j=prime[i];j<maxn;j+=prime[i])
		fac[j].push_back(prime[i]);
	}
}
void add(int x,int v)
{
	for(int i=x;i<=n;i+=i&-i)
	tree[i]+=v;
}
int sum(int x)
{
	int sum=0; 
	for(int i=x;i>0;i-=i&-i)
	sum+=tree[i];
	return sum;
}
int main()
{
	init();
	while(~scanf("%d%d",&n,&m)&&n+m)
	{
		for(int i=0;i<=n;++i)
		{
		 if(i)scanf("%d",&a[i]); 
		 l[i]=0;r[i]=n+1;
	    }
	    for(int i=0;i<maxn;++i)
	    {
	    	pos[i]=n+1;
	    	to[i].clear();
	    }
	    //先处理右端界 
		for(int i=n;i>=1;--i)
		{
			int len=fac[a[i]].size();
			for(int j=0;j<len;++j)
			{
				int v=fac[a[i]][j];
				r[i]=min(r[i],pos[v]);
				pos[v]=i;
			}
		}
		memset(pos,0,sizeof pos);
		//再处理左端界 
	    for(int i=1;i<=n;++i)
	    {
	    	int len=fac[a[i]].size();
            for(int j=0;j<len;++j)
            {
            	int v=fac[a[i]][j];//a[i]的因子v  
            	l[i]=max(l[i],pos[v]);
            	pos[v]=i;//因子v当前出现的最大位置 
            }
            to[l[i]].push_back(i);//位置l[i]处映射i  
	    }
	    for(int i=0;i<m;++i)
	    {
	    	scanf("%d%d",&q[i].l,&q[i].r);
	    	q[i].id=i;
	    }
	    sort(q,q+m,cmp);
	    memset(tree,0,sizeof tree);
	    //注意操作在0和n+1上的对答案并不会造成影响 
		for(int i=0,j=0;i<=n;++i)
		{
			for(;j<m&&q[j].l==i;++j)
			ans[q[j].id]=sum(q[j].r)-sum(q[j].l-1);
			int len=to[i].size();
			for(int k=0;k<len;++k)
			{
				int x=to[i][k];
				add(x,1);
				add(r[x],-1);
		    }
		    if(i)add(i,-1);//防止i==0导致的死循环 
			if(r[i])add(r[i],1);//消除这个值对以后的影响 
		}
		for(int i=0;i<m;++i)
		printf("%d\n",ans[i]); 
	}
	return 0;
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值