P3455 [POI2007]ZAP-Queries

题面:**

Link

太长了,懒得粘了

懵逼乌斯反演入门题。

首先,我们要求的是这个柿子:

\(\displaystyle \sum_{i=1}^{n}\sum_{j=1}^{m} [gcd(i,j) == k]\) (这里的 \(n,m\) 即题面中的 \(a,b\)\(k\) 为题面中的 \(d\))

为了省事(偷懒) 我们在这里默认 \(n \leq m\)

按套路,提取一个 \(k\) 出来变成:

\(\displaystyle\sum_{i=1}^{n\over k} \sum_{j=1}^{m\over k} [gcd(i,j) == 1]\)

由莫比乌斯反演可以得到一个等式:

\(\displaystyle [gcd(i,j) == 1] = \sum_{d=1,d\mid i,d\mid j}^{n} \mu(d)\)

把这个柿子回代到原来的柿子可得:

\(\displaystyle\sum_{i=1}^{n\over k} \sum_{j=1}^{m\over k}\sum_{d\mid i, d\mid k} \mu(d)\)

先枚举一下 \(d\) 变成:

\(\displaystyle\sum_{d=1}^{n\over k} \mu(d) \sum_{i=1}^{n\over k} [d\mid i] \sum_{j = 1}^{m\over k}[d\mid k]\)

在变换一下可以变成:

\(\displaystyle\sum_{d=1}^{n\over k} \mu(d) \lfloor{n\over kd}\rfloor\lfloor{m\over kd}\rfloor\)

这就是我们最后的总柿子。

在对 \(\mu\) 函数值求个前缀和,在套个数论分块即可。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const int N = 5e4+10;
int t,n,m,d,tot;
int prime[N],mu[N],sum[N];
bool check[N];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
	return s * w;
}
void YYCH()
{
	mu[1] = 1;
	for(int i = 2; i <= N-5; i++)//筛 mu 函数
	{
		if(!check[i])
		{
			prime[++tot] = i;
			mu[i] = -1;
		}
		for(int j = 1; j <= tot && i * prime[j] <= N-5; j++)
		{
			check[i * prime[j]] = 1;
			if(i % prime[j] == 0)
			{
				mu[i * prime[j]] = 0;
				break;
			}
			else
			{
				mu[i * prime[j]] = -mu[i];
			}
		}
	}
	for(int i = 1; i <= N-5; i++)//前缀和
	{
		sum[i] = sum[i-1] + mu[i];
	}
}
LL slove(int n,int m,int d)
{
	LL res = 0;
	for(int l = 1,r; l <= n/d; l = r+1)//数论分块
	{
		r = min(n/(n/l),m/(m/l));
		res += 1LL * (sum[r] - sum[l-1]) * (n/l/d) * (m/l/d);
	}
	return res;
}
int main()
{
	t = read(); YYCH();
	while(t--)
	{
		n = read(); m = read(); d = read();
		if(n > m) swap(n,m);
		printf("%lld\n",slove(n,m,d));
	}
	return 0;
}
posted @ 2020-09-14 21:44  genshy  阅读(86)  评论(0编辑  收藏  举报