题面
麦克雷有一个 1~n 的排列,他想知道对于一些区间,有多少对区间内的数(x,y),满足 x 能被 y 整除。
第一行包含 2 个正整数 n,m。表示有 n 个数,m 个询问。
30%:1<=n,m<=100 100%:1<=n,m<=2*1e5 ,1<=pi<=n。
分析
一看就是线段树or树状数组题,和以前那个mushroom的区间画风真的像
1到n的排列也是个很特殊的信息。其实很容易想到把每个数的倍数预处理出来,再通过区间相减求答案。
然而,这种做法忽略了,你要查询的区间中的一个数的因数或倍数在这个区间之外。所以,为了防止这种情况,我们需要固定右端点。
右端点升序排列,处理出所有在右端点的数范围内的倍数和因数,在线查询,在线更新即可。
先开始想的枚举每个数的因数是√n * n 的时间复杂度,然而n/1+n/2+n/3+n/4+……+n/n = nlogn,所以总复杂度O((nlogn+m)log n)
代码
#include<bits/stdc++.h> using namespace std; #define N 200020 #define ll long long ll n,m,cnt=1; ll a[N],t[N],pos[N]; struct email { ll l,r,id; ll ans; }q[N]; vector<ll>v[N]; bool cmp1(email x,email y){return x.r<y.r;} bool cmp2(email x,email y){return x.id<y.id;} inline ll lowbit(ll x){return x&(-x);} inline void add(ll x) { for(;x<=n;x+=lowbit(x)) t[x]++; } inline ll query(ll x) { ll ret=0; for(;x;x-=lowbit(x)) ret+=t[x]; return ret; } int main() { scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++)scanf("%lld",&a[i]),pos[a[i]]=i; for(ll i=1;i<=m;i++)scanf("%lld%lld",&q[i].l,&q[i].r),q[i].id=i; sort(q+1,q+1+m,cmp1); for(ll i=1;i<=n;i++) for(ll j=i;j<=n;j+=i) { if(i!=j)v[pos[j]].push_back(i); v[pos[i]].push_back(j); } for(ll i=1;i<=n;i++) { ll siz=v[i].size(); for(ll j=0;j<siz;j++) if(pos[v[i][j]]<=i) add(pos[v[i][j]]); while(q[cnt].r==i)q[cnt].ans=query(q[cnt].r)-query(q[cnt].l-1),++cnt; } sort(q+1,q+1+m,cmp2); for(ll i=1;i<=n;i++)printf("%lld ",q[i].ans); return 0; }