P5398 [Ynoi2018] GOSICK 题解
第十四分块的题目,我们考虑莫队二次离线。
题目中要求出满足\(l \le x,y \le r\)且\(a_l | a_r\)的个数。我们考虑定义函数\(f(a,b,c)\)表示满足\(x=a,b \le y \le c\)的答案的个数,那么一次询问就是求\(\sum_{i=l}^{i \le r} f(i,l,r)\)我们进行一个转换,有一个显然的事实,我们找出\(x < y < z\)那么对于任意\(i\)均有\(f(i,y,z)=f(i,x,z)-f(i,x,y-1)\)这一事实,所以我们可以把问题转化为\(\sum_{i=1}^{i\le r}f(i,l,r)-\sum_{i=1}^{i\le l-1}f(i,l,r)\)这就是莫队二次离线。
我们考虑普通莫队,那么我们会移动\(O(n\sqrt n)\)次,运用上述柿子进行计算,我们每一次移动相当于求出一个点对一个前缀的答案,然后对于\(a_i\)的因数在\(5e5\)内可以看作\(O(\sqrt n)\),对于\(a_i\)的倍数,在\(a_i \le 100\)的时候每一次都可以遍历整个序列离线快速处理出来倍数的个数,对于\(a_i > 100\)的时候可以开一个桶解决这个问题。(关于为什么要用\(100\),不用\(\sqrt n\),这是因为后半部分常数显然大于前半部分,用小的闸值会让你跑得更快。)
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=500010;
const int V=500000;
int tp,n,m,B,lim,cc[maxn],a[maxn],b1[maxn],b2[maxn],cnt1[maxn],cnt2[maxn];
long long f1[maxn],f2[maxn],ans[maxn],lin,ci;
vector<int>F[maxn];
struct edge{
int l;
int r;
int id;
int b;
long long sum;
}xun[maxn];
struct node{
int l;
int r;
int p;
int id;
}Q[maxn<<1];
int cmp(edge q,edge w){
if(q.b!=w.b){
return q.b<w.b;
}
if(q.b&1){
return q.r<w.r;
}
return q.r>w.r;
}
int cp(node q,node w){
return q.p<w.p;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
B=sqrt(n);
lim=100;
for(int i=1;i<=n;i++){
cin>>a[i];
if(!F[a[i]].size()){
for(int j=1;j*j<=a[i];j++){
if(a[i]%j==0){
F[a[i]].push_back(j);
b1[j]++,f1[i]+=cc[j];
if(j*j!=a[i]){
b1[a[i]/j]++;
f1[i]+=cc[a[i]/j];
}
}
}
}
else{
for(int j=0;j<F[a[i]].size();j++){
b1[F[a[i]][j]]++;
f1[i]+=cc[F[a[i]][j]];
if(F[a[i]][j]*F[a[i]][j]!=a[i]){
b1[a[i]/F[a[i]][j]]++;
f1[i]+=cc[a[i]/F[a[i]][j]];
}
}
}
f1[i]+=f1[i-1]+b1[a[i]];
cc[a[i]]++;
}//预分解因数
for(int i=1;i<=m;i++){
cin>>xun[i].l>>xun[i].r;
xun[i].sum=0;
xun[i].id=i;
xun[i].b=(xun[i].l-1)/B+1;
}
sort(xun+1,xun+1+m,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
if(r<xun[i].r){
xun[i].sum+=f1[xun[i].r]-f1[r];
Q[++tp]=(node){r+1,xun[i].r,l-1,-i};
r=xun[i].r;
}
if(r>xun[i].r){
xun[i].sum-=f1[r]-f1[xun[i].r];
Q[++tp]=(node){xun[i].r+1,r,l-1,i};
r=xun[i].r;
}
if(l>xun[i].l){
xun[i].sum-=f1[l-1]-f1[xun[i].l-1];
Q[++tp]=(node){xun[i].l,l-1,r,i};
l=xun[i].l;
}
if(l<xun[i].l){
xun[i].sum+=f1[xun[i].l-1]-f1[l-1];
Q[++tp]=(node){l,xun[i].l-1,r,-i};
l=xun[i].l;
}
}//一次存一段对一个点,不然空间开不下。
sort(Q+1,Q+1+tp,cp);
int p=0;
for(int i=1;i<=tp;i++){
while(p<Q[i].p){
p++;
for(int j=0;j<F[a[p]].size();j++){
b2[F[a[p]][j]]++;
if(F[a[p]][j]*F[a[p]][j]!=a[p]){
b2[a[p]/F[a[p]][j]]++;
}
}//因数
if(a[p]>lim){
for(int j=1;j*a[p]<=V;j++){
b2[j*a[p]]++;
}
}//大于100的倍数
}
for(int j=Q[i].l;j<=Q[i].r;j++){
if(Q[i].id<0){
xun[-Q[i].id].sum-=b2[a[j]];
}
else{
xun[Q[i].id].sum+=b2[a[j]];
}
}
ci=ci+Q[i].r-Q[i].l+1;
}
for(int j=1;j<=lim;j++){
cnt1[0]=0;
cnt2[0]=0;
for(int i=1;i<=n;i++){
cnt1[i]=cnt1[i-1]+(a[i]==j);
cnt2[i]=cnt2[i-1]+(a[i]%j==0);
}
for(int i=1;i<=tp;i++){
lin=cnt1[Q[i].p]*(cnt2[Q[i].r]-cnt2[Q[i].l-1]);
if(Q[i].id<0){
xun[-Q[i].id].sum-=lin;
}
else{
xun[Q[i].id].sum+=lin;
}
}
}//小于100的倍数
for(int i=1;i<=m;i++){
xun[i].sum+=xun[i-1].sum;
ans[xun[i].id]=xun[i].sum;
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<'\n';
}
return 0;
}