luogu3327 [SDOI2015]约数个数和

link

\(d(x)\)表示x约数个数,给定n,m,\(\sum_{i=1}^n\sum_{j=1}^md(ij)\)

多组询问,1<=T<=50000,1<=N, M<=50000

前置知识:\(d(ij)=\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]\)

证明:\(xy\)一定是\(ij\)的约数。考虑一个质数\(p\)\(i\)中包含\(p^a\)\(j\)中包含\(p^b\),则\(ij\)中包含的是\(p^{a+b}\)。若\(\gcd(x,y)=1\),说明\(x,y\)中至少有一个数的\(p^k\)为1,容斥一下就有\(a+b+1\)种,而\(p^{a+b}\)中也恰好有\(a+b+1\)的贡献。最后把所有质数贡献乘起来就是答案。

推式子即可:

\(\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]\)

\(=\sum_{x=1}^n\sum_{y=1}^m\lfloor\frac nx\rfloor\lfloor\frac my\rfloor[\gcd(x,y)=1]\)

\(=\sum_{x=1}^n\sum_{y=1}^m\lfloor\frac nx\rfloor\lfloor\frac my\rfloor\sum_{d|x,d|y}\mu(d)\)

\(=\sum_{d=1}^n\mu(d)\sum_{x=1}^{n/d}\sum_{y=1}^{m/d}\lfloor\frac n{xd}\rfloor\lfloor\frac m{yd}\rfloor\)

\(f(n)=\sum_{i=1}^n\lfloor\frac ni\rfloor=\sum_{i=1}^nd(i)\),这里的\(d\)是约数个数,由于\(n\le50000\),这个可以预处理线性筛约数个数

则原式=\(\sum_{d=1}^n\mu(d)f(\lfloor\frac nd\rfloor)f(\lfloor\frac md\rfloor)\),直接上数论分块


#include <cstdio>
#include <functional>
using namespace std;

int prime[50010], fuck = 50000, tot, d[50010], d1[50010], mu[50010];
bool vis[50010];

int main()
{
	mu[1] = d[1] = d1[1] = 1;
	for (int i = 2; i <= fuck; i++)
	{
		if (vis[i] == false) { prime[++tot] = i, mu[i] = -1, d[i] = d1[i] = 2; }
		for (int j = 1; j <= tot && i * prime[j] <= fuck; j++)
		{
			vis[i * prime[j]] = true;
			if (i % prime[j] == 0)
			{
				d1[i * prime[j]] = d1[i] + 1;
				d[i * prime[j]] = d[i] / d1[i] * d1[i * prime[j]];
				break;
			}
			d1[i * prime[j]] = 2;
			d[i * prime[j]] = d[i] * 2;
			mu[i * prime[j]] = -mu[i];
		}
		d[i] += d[i - 1];
		mu[i] += mu[i - 1];
	}
	int t; scanf("%d", &t);
	while (t --> 0)
	{
		int n, m;
		long long ans = 0;
		scanf("%d%d", &n, &m);
		if (n > m) swap(n, m);
		for (int i = 1, j; i <= n; i = j + 1)
		{
			j = min(n / (n / i), m / (m / i));
			ans += (mu[j] - mu[i - 1]) * (long long)d[n / i] * d[m / i];
		}
		printf("%lld\n", ans);
	}
	return 0;
}

45行一遍AC

最近做数论题都不用观察题解了。。。

posted @ 2019-01-21 15:19  ghj1222  阅读(157)  评论(0编辑  收藏  举报