题目大意
给出一个排列,每次询问一个区间中满足一个数为另一个数倍数的数对个数.
分析
首先可以发现整个排列的合法数对数为 (mathcal{O}(nlog n)) 级别,所以可以想到直接去维护这些数对.一个数对由两个数组成,这就不容易处理,可以想到离线去处理.对查询的右端点排序.显然可以在右端点移动时将新加入的数的贡献加入.那么现在就变成了一个后缀查询的形式,既然是后缀,不妨将一个数对的贡献放在靠前的数中,这样在查询中只有当两个数都在这个后缀中的时候才会产生贡献.于是问题变成了单点加,区间查询,可以用树状数组轻松维护.
时间复杂度 (mathcal{O}(nlog^2n)).如果您有低于这个复杂度的做法可以私信我/kel.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(register int i=(first);i<=(last);++i)
#define DOW(i,first,last) for(register int i=(first);(last)<=i;--i)
#define LL long long
#define UI unsigned int
#define ULL unsigned long long
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLI pair<long long,int>
#define PLL pair<long long,long long>
#define MPR(a,b) make_pair(a,b)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int MAXN=2e5+5;
int n,m;
int arr[MAXN];
int id[MAXN];
vector<int>link[MAXN];
class Query
{
public:
int left,right,id;
inline bool operator <(const Query &a)const
{
return right<a.right;
}
}q[MAXN];
int answer[MAXN];
namespace BIT//单点修改+区间查询的树状数组
{
int tree[MAXN];
inline int Lowbit(const int now)
{
return now&-now;
}
inline void Updata(const int place)
{
for(register int now=place;now<=n;now+=Lowbit(now))
{
++tree[now];
}
}
inline int Query(const int left,const int right)
{
register int result=0;
for(register int now=right;now;now-=Lowbit(now))
{
result+=tree[now];
}
for(register int now=left-1;now;now-=Lowbit(now))
{
result-=tree[now];
}
return result+right-left+1;
}
}
int main()
{
Read(n,m);
REP(i,1,n)
{
Read(arr[i]);
id[arr[i]]=i;
}
REP(i,1,n)//将数对的贡献放在靠前的数中
{
int top=n/arr[i];
REP(j,2,top)
{
if(id[arr[i]*j]<i)
{
link[i].push_back(id[arr[i]*j]);
}
else
{
link[id[arr[i]*j]].push_back(i);
}
}
}
REP(i,1,m)
{
Read(q[i].left,q[i].right);
q[i].id=i;
}
sort(q+1,q+1+m);
int now=1;
REP(i,1,n)
{
if(link[i].size())
{
REP(j,0,link[i].size()-1)//将新加入的数的贡献放入
{
BIT::Updata(link[i][j]);
}
}
while(q[now].right==i)
{
answer[q[now].id]=BIT::Query(q[now].left,q[now].right);
++now;
}
}
REP(i,1,m)
{
Writeln(answer[i]);
}
return 0;
}