1. 程式人生 > 實用技巧 >P3327 [SDOI2015]約數個數和

P3327 [SDOI2015]約數個數和

Link

一句話題意

\(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{m} d(ij)\)

題解:

首先 \(d\) 函式有個性質, \(d(i,j) == \displaystyle\sum_{x \mid i}\sum_{y\mid j} [gcd(i,j) == 1]\)

然後我們要求的就是 :

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

直接上莫比烏斯反演,變成:

\(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x\mid i}\sum_{y \mid j} \sum_{p\mid x,p\mid y} \mu(p)\)

考慮到這麼多的列舉量不太好算,所以我們需要減少一下列舉量。

先列舉一下 \(x\)\(y\) 試試,變成:

\(\displaystyle\sum_{x=1}^{n}\sum_{j=1}^{m}\sum_{x \mid i}\sum_{y\mid j}\sum_{p\mid x,p \mid y} \mu(p)\)

發現倒數第三個和倒數第四個其實求的是 \(1-n\)\(x\) 的倍數,以及 \(1-m\)\(y\) 的倍數。

就可以寫成:

\(\displaystyle\sum_{x=1}^{n}\sum_{y=1}^{m}\lfloor {n\over x}\rfloor\lfloor{m \over j}\rfloor \sum_{p \mid x,p\mid j} \mu(p)\)

先列舉一下 \(p\) 試試,

\(\displaystyle\sum_{p=1}^{n}\mu(p) \sum_{x=1}^{n}[p\mid x]\sum_{j=1}^{m} [p\mid j] \lfloor {n\over x}\rfloor\lfloor{m\over j}\rfloor\)

中間那兩個柿子還可以在化簡一下變成:

\(\displaystyle\sum_{p=1}^{n}\mu(p)\sum_{x=1}^{n\over d}\sum_{y=1}^{y\over d} \lfloor{n\over px}\rfloor\lfloor{m\over py}\rfloor\)

把兩個向下取整拆開變為:

\(\displaystyle\sum_{p=1}^{n}\mu(p)\sum_{x=1}^{n\over p} \lfloor{n\over px}\rfloor\sum_{j=1}^{m\over p}\lfloor{m\over py}\rfloor\)

\(g(n)\) 表示 \(\displaystyle\sum_{i=1}^{n} {n\over i}\)

那麼上面的柿子可以寫成 \(\displaystyle\sum_{p=1}^{n}\mu(p) g({n\over p}) g({m\over p})\)

對於 \(g\) 我們可以利用整除分塊求出來,最後的柿子也要搞個整除分塊,所以就是整除分塊套整除分塊(老千層餅了

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
const int N = 50010;
int n,m,T,tot;
LL mu[N],sum[N],prime[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++)
	{
		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 = 2; i <= N-5; i++) mu[i] += mu[i-1]; //求mu的字首和
	for(int i = 1; i <= N-5; i++)
	{
		for(int l = 1, r; l <= i; l = r+1)//求g函式值
		{
			r = min(i,(i/(i/l)));
			sum[i] += (r-(l-1)) * (i/l); 
		}
	}
}
LL slove(int n,int m)
{
	LL res = 0;
	for(int l = 1, r; l <= n; l = r+1)//套個整除分塊
	{
		r = min(n/(n/l),m/(m/l));
		res += (mu[r]-mu[l-1]) * sum[n/l] * sum[m/l];
	}
	return res;
}
int main()
{
	T = read(); YYCH();
	while(T--)
	{
		n = read(); m = read();
		if(n > m) swap(n,m);
		printf("%lld\n",slove(n,m));
	}
	return 0;
}