【洛谷P3312】数表
题目
题目链接:https://www.luogu.com.cn/problem/P3312
有一张 \(n\times m\) 的数表,其第 \(i\) 行第 \(j\) 列(\(1\le i\le n\),\(1\le j\le m\))的数值为能同时整除 \(i\) 和 \(j\) 的所有自然数之和。给定 \(a\),计算数表中不大于 \(a\) 的数之和。
思路
先不考虑 \(a\) 的限制,那么 \((i,j)\) 的数值即为 \(\gcd(i,j)\) 的因子之和(设为 \(g(i)\))。\(g(i)\) 可以 \(O(n\log n)\) 预处理出。
\[ans=\sum^{n}_{i=1}g(i)\times \sum^{\min(n,m)}_{d|i}\mu(\frac{i}{d})\lfloor{\frac{n}{i}}\rfloor\lfloor{\frac{m}{i}}\rfloor
\]
\[=\sum^{n}_{i=1}\lfloor{\frac{n}{i}}\rfloor\lfloor{\frac{m}{i}}\rfloor\sum^{\min(n,m)}_{i|d}g(d)\mu(\frac{i}{d})
\]
当有 \(a\) 的限制时,只有 \(g(x)\leq a\) 的 \(g(x)\) 才可以产生贡献。所以我们将询问按 \(a\) 排序,数字 \(x\) 按 \(g(x)\) 排序,对于一个询问 \(a\) 将 \(g(x)\leq a\) 的所有 \(g(x)\) 对 \(x\) 倍数的贡献加上,然后再询问一段区间的和。
用树状数组处理即可。然后就是整除分块乱搞了。
时间复杂度 \(O(Q\sqrt{n}+Q\log^2 n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned int uint;
const int N=100010;
int Q,tot,prm[N],mu[N],g[N];
uint ans[N];
bool v[N];
struct Query
{
int n,m,a,id;
}ask[N];
struct node
{
int g,id;
}a[N];
bool cmp1(Query x,Query y)
{
return x.a<y.a;
}
bool cmp2(node x,node y)
{
return x.g<y.g;
}
void findprm(int n)
{
mu[1]=1;
for (int i=2;i<=n;i++)
{
if (!v[i])
prm[++tot]=i,mu[i]=-1;
for (int j=1;j<=tot;j++)
{
if (i>n/prm[j]) break;
v[prm[j]*i]=1; mu[prm[j]*i]=-mu[i];
if (!(i%prm[j]))
{
mu[i*prm[j]]=0;
break;
}
}
}
}
struct BIT
{
uint c[N];
void add(int x,uint v)
{
for (int i=x;i<N;i+=i&-i)
c[i]+=v;
}
uint query(int x)
{
uint ans=0;
for (int i=x;i;i-=i&-i)
ans+=c[i];
return ans;
}
}bit;
int main()
{
findprm(N-10);
for (int i=1;i<=N-10;i++)
for (int j=i;j<=N-10;j+=i)
g[j]+=i;
for (int i=1;i<=N-10;i++)
a[i]=(node){g[i],i};
sort(a+1,a+1+N-10,cmp2);
scanf("%d",&Q);
for (int i=1;i<=Q;i++)
{
scanf("%d%d%d",&ask[i].n,&ask[i].m,&ask[i].a);
ask[i].id=i;
}
sort(ask+1,ask+1+Q,cmp1);
for (int i=1,j=1;i<=Q;i++)
{
for (;j<=N-10 && a[j].g<=ask[i].a;j++)
for (int k=a[j].id;k<=N-10;k+=a[j].id)
bit.add(k,1U*mu[k/a[j].id]*a[j].g);
int n=ask[i].n,m=ask[i].m;
for (int l=1,r;l<=min(n,m);l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans[ask[i].id]+=1U*(n/l)*(m/l)*(bit.query(r)-bit.query(l-1));
}
}
for (int i=1;i<=Q;i++)
printf("%d\n",(int)(2147483647U&ans[i]));
return 0;
}