传送门
题意:给你一个长度为n的排列,询问m次区间,问区间 [ l, r ] 内有多少对 i,j 使得 gcd(pi,pj)= min(pi,pj)。
题解: 对于每个数我可以处理出它倍数所在的位置,假设 gcd(pi,pj)= min(pi,pj), 如果 i < j , 那么在vector[ j ] .push_back( i ) ,否则vector[ i ] .push_back( j ), 也就是每个点储存在它前面且与它互为倍数的点的位置,这样用主席树维护一下,在第i个版本插入之前储存的点。
//https://nanti.jisuanke.com/t/41391
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n, m;
int a[N];
vector<int> v[N];
int rt[N], ls[N*200], rs[N*200], num[N*200], cnt = 0;
void up(int &o, int pre, int l, int r, int k) {
o = ++cnt;
ls[o] = ls[pre];
rs[o] = rs[pre];
num[o] = num[pre] + 1;
if (l == r)
return;
int mid = (l+r)>> 1;
if (k <= mid)
up(ls[o], ls[pre], l, mid, k);
else
up(rs[o], rs[pre], mid + 1, r, k);
}
int qu(int o, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr)
return num[o];
int res = 0;
int mid = (l+r)>> 1;
if (ql <= mid)
res += qu(ls[o], l, mid, ql, qr);
if (qr > mid)
res += qu(rs[o], mid + 1, r, ql, qr);
return res;
}
int main(){
scanf("%d%d", &n, &m);
int c;
for(int i = 1; i <= n; i++){
scanf("%d", &c);
a[c] = i;
}
for(int i = 1; i <= n; i++){
for(int j = 2*i; j <= n; j += i){
if(a[i] > a[j]) v[a[i]].push_back(a[j]);
else v[a[j]].push_back(a[i]);
}
}
for(int i = 1; i <= n; i++){
rt[i] = rt[i-1];
for(auto u :v[i]){
up(rt[i], rt[i], 1, n, u);
}
}
int l, r, ans = 0;
for(int i = 1; i <= m; i++){
scanf("%d%d", &l, &r);
ans = qu(rt[r], 1, n, l, n);
printf("%d\n", ans);
}
return 0;
}