[SDOI2014]数表
Link
Description
\(q\) 次询问,每次给出 \(n\)、\(m\) 和 \(a\),求
\[\sum_{i=1}^{n}\sum_{j=1}^m \sigma_1(gcd(i,j))[\sigma_1(gcd(i,j))\leq a]
\]
Solution
按套路枚举 \(gcd(i,j)\),记 \(c=min(n,m)\)
\[\begin{align}
(*)&=\sum_{d=1}^{c} \sigma_1(d)[\sigma_1(d)\leq a] \sum_{i=1}^{n}\sum_{j=1}^m [gcd(i,j)=d] \\
&=\sum_{d=1}^{c} \sigma_1(d)[\sigma_1(d)\leq a] \sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor} \sum_{k|i,k|j} \mu(k) \\
&=\sum_{d=1}^{c} \sigma_1(d)[\sigma_1(d)\leq a] \sum_{k=1}^{\lfloor \frac{n}{d} \rfloor} \mu(k) \lfloor\frac{n}{kd}\rfloor \lfloor\frac{m}{kd}\rfloor
\end{align}
\]
令 \(T=kd\)
\[\begin{align}
(*)&=\sum_{T=1}^c \lfloor\frac{n}{d}\rfloor \lfloor\frac{m}{d}\rfloor \sum_{d|T} [\sigma_1(d)\leq a]\sigma_1(d)\mu(\frac{T}{d})
\end{align}
\]
如果没有 \(a\) 的限制,那应该相当好做。外和可以整数分块,内和是积性函数,可以线性筛预处理,再做前缀和。但加了 \(a\) 的限制,内和是动态的,怎么维护?
询问间相互独立,所以能想到将询问按 \(a\) 升序离线下来。那么每次询问之前,只需要把 \(\sigma_1(d)\) 介于 \(a_{i-1}+1\) 到 \(a_i\) 的 \(d\) 加入到数组中。每个 \(d\) 只会被加一遍。每个 \(d\) 会影响 \(\frac{n}{d}\) 个内和,那么总修改次数就是调和级数,为 \(O(n \log n)\) 。而我们求和的时候是查询一个区间的内和,所以想到用树状数组直接维护动态前缀和。
总复杂度 \(O(q\sqrt{n}log_n+n \log^2 n)\) 。
#include<stdio.h>
#include<algorithm>
using namespace std;
#define M 20007
#define N 100007
#define ll unsigned int
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
struct Que{
int n,m,a,pos;
bool operator <(const Que &X) const{return a<X.a;}
}q[M];
struct Node{
int seg,d;
Node(int seg_=0,int d_=0):seg(seg_),d(d_){}
bool operator <(const Node &X) const{return seg<X.seg;}
}a[N];
int T,p[N],cnt=0,d[N];
ll ans[M],mu[N],seg[N],c[N];
bool mk[N];
inline int lowbit(int x){return -x&x;}
inline void add(int x,ll v){while(x<N)c[x]+=v,x+=lowbit(x);}
inline ll query(int x){ll ret=0;while(x)ret+=c[x],x-=lowbit(x);return ret;}
inline int min(int x,int y){return x<y? x:y;}
int main(){
T=read();
for(int i=1;i<=T;i++)
q[i].n=read(),q[i].m=read(),q[i].a=read(),q[i].pos=i;
sort(q+1,q+1+T);
seg[1]=mu[1]=1; a[1]=Node(1,1);
for(int i=2;i<N;i++){
if(!mk[i])
d[i]=p[++cnt]=i,seg[i]=i+1,mu[i]=-1;
for(int j=1;j<=cnt&&p[j]*i<N;j++){
mk[p[j]*i]=1;
if(i%p[j]){
mu[i*p[j]]=-mu[i];
d[i*p[j]]=p[j];
seg[i*p[j]]=seg[i]*seg[p[j]];
}else{
d[i*p[j]]=d[i]*p[j];
if(d[i*p[j]]!=i*p[j])
seg[i*p[j]]=seg[i/d[i]]*seg[d[i*p[j]]];
else seg[i*p[j]]=seg[i]*p[j]+1;
break;
}
}
a[i]=Node(seg[i],i);
}
sort(a+1,a+N); int pos=1;
for(int i=1;i<=T;i++){
while(pos<N&&a[pos].seg<=q[i].a){
for(int j=1;j*a[pos].d<N;j++)
add(j*a[pos].d,a[pos].seg*mu[j]);
pos++;
}
int n=q[i].n,m=q[i].m;
ll ret=0; int rg=min(n,m);
for(int l=1,r;l<=rg;l=r+1){
r=min(n/(n/l),m/(m/l));
ret+=(n/r)*(m/r)*(query(r)-query(l-1));
}
ans[q[i].pos]=ret;
}
for(int i=1;i<=T;i++) printf("%d\n",ans[i]&((1ll<<31)-1));
}