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

被折叠的 条评论
为什么被折叠?



