【bzoj3994】 SDOI2015—约数个数和

http://www.lydsy.com/JudgeOnline/problem.php?id=3994 (题目链接)

题意

  多组询问,给出${n,m}$,求${\sum_{i=1}^n\sum_{j=1}^m d(i×j)}$,${d(i×j)}$为${ij}$的约数个数。

Solution

  看到这个式子感觉无从下手,这个${d(ij)}$比较丑,有一个比较经典的公式:$${d(nm)=\sum_{i|n}\sum_{j|m} [gcd(i,j)=1]}$$

  这个是怎么得来的呢。每一个${nm}$的约数都可以表示成这样的形式:${i×\frac{m}{j}}$。其中${i}$是${n}$的约数,${j}$是${m}$的约数。

  那么如果我们直接枚举${i,j}$来统计的话,可能会有重复,所以就要加上一个条件:${[gcd(i,j)=1]}$,也就是${i,j}$互质。那他们不互质为什么就不行呢,假设${i,j}$都含有一个约数${p}$,那么${\frac{m}{j}}$就表示从${m}$中拿掉${j}$用剩下的去组成约数,也就是从约数中拿掉了${p}$;而${i}$就是从${n}$中拿${i}$去组成约数,也就向约数中加入了${p}$。那么我们拿掉一个${p}$又加入一个${p}$,不是吃多了吗→_→,这样自然会算重复啦。

  解决了这个问题,我们就很好做了。开始推式子。

\begin{aligned}  \sum_{i=1}^{n}\sum_{j=1}^{m}d(ij) =& \sum_{i=1}^n\sum_{j=1}^m\sum_{u|i}\sum_{v|j}[gcd(u,v)=1]  \\  =&\sum_{u=1}^n\sum_{v=1}^m[gcd(u,v)=1]\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor  \\  =&\sum_{t=1}^nμ(t)\sum_{i=1}^{\lfloor{n/t}\rfloor}\sum_{j=1}^{\lfloor{m/t}\rfloor}\lfloor\frac{n}{ti}\rfloor\lfloor\frac{m}{tj}\rfloor  \\  =&\sum_{t=1}^nμ(t)\sum_{i=1}^{\lfloor{n/t}\rfloor}\lfloor\frac{\lfloor{n/t}\rfloor}{i}\rfloor\sum_{j=1}^{\lfloor{m/t}\rfloor}\lfloor\frac{\lfloor{m/t}\rfloor}{j}\rfloor   \end{aligned}

  我们令${f(n)=\sum_{i=1}^n\lfloor{n/i}\rfloor}$,那么${f}$是可以分段${O(n\sqrt{n})}$的预处理出来的。$${原式=\sum_{t=1}^{n}μ(t)f(\lfloor{n/t}\rfloor)f(\lfloor{m/t}\rfloor)}$$

  这样我们就可以预处理出${μ}$的前缀和,然后分段求解答案就可以了。

细节

  LL,预处理不要太多会TLE。

代码

// bzoj3994
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#define LL long long
#define inf 2147483640
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;

const int maxn=50010;
LL n,m,s[maxn],mu[maxn],f[maxn];
int p[maxn],vis[maxn];

int main() {
	int T;scanf("%d",&T);
	s[1]=mu[1]=1;
	for (int i=2;i<maxn;i++) {
		if (!vis[i]) p[++p[0]]=i,mu[i]=-1;
		for (int j=1;j<=p[0] && p[j]*i<maxn;j++) {
			vis[i*p[j]]=1;
			if (i%p[j]==0) {mu[i*p[j]]=0;break;}
			else mu[i*p[j]]=-mu[i];
		}
		s[i]=s[i-1]+mu[i];
	}
	for (int i=1;i<maxn;i++)
		for (int j=1,k;j<=i;j=k+1) {
			k=i/(i/j);
			f[i]+=(LL)(k-j+1)*(i/j);
		}
	while (T--) {
		scanf("%lld%lld",&n,&m);
		if (n>m) swap(n,m);
		LL ans=0;
		for (int i=1,j;i<=n;i=j+1) {
			j=min(n/(n/i),m/(m/i));
			ans+=f[n/i]*f[m/i]*(s[j]-s[i-1]);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

 

posted @ 2017-02-04 15:00  MashiroSky  阅读(927)  评论(0编辑  收藏  举报