完全平方数


完全平方数

这是将我引上莫比乌斯反演的第一题 , 传送门:完全平方数

题目大意:求第 \(k\) 个不是完全平方数的倍数的数。

暴力似乎很爽,确实。
但可以以一个优美的算法切掉这一题—— \(O(\log n\sqrt n)\)
优雅啊!

在一个大佬的帮助下——初一被清华锁定的 lzc 大佬——我明白了这道经典题的解法。

进入正题:

注:[p]表示,若 p 为真,其值为 1,否则为 0

莫比乌斯函数 \(\mu\):默认你已懂其数学定义。

性质一:$$\sum_{d|n}\mu(d) = [n=1]$$

略作证明:

前置知识——二项式定理:$$(a+b)^n = \sum_{k=0}^n \binom{n}{k} a^k b^{n - k}$$
证明略。
现证性质一
\(\omega(n)\) 表示 \(n\) 的不同质因子的个数

\(n>1\),则有:

\[\begin{aligned} \sum_{d|n}\mu(d) &=\sum_{i=0}^{\omega(n)} \binom{\omega(n)}i (-1)^i \\ &=\sum_{i=0}^{\omega(n)} \binom{\omega(n)}i (-1)^i 1^{\omega(n)-i} \\ &=(1-1)^{\omega(n)} \\ &=0 \end{aligned} \]

\(n=1\),则易得$$\sum_{d|n} \mu(d) = 1$$
证毕。
那么如何做此题呢?
因为 \(\mu\) 的定义,我们很容易想到如果一个数是完全平方数,那么它的 \(\mu\) 就必然是 \(0\),否则就是 \(1\)\(-1\)
那第 \(k\) 个非完全平方数的倍数,记作\(n\) , 就要满足 \(k = \sum_{i=1}^n\mu^2(i)\) 且是所有满足此条中最小的(不要问我为何,你要知道,若 \(n\) 是完全平方数的倍数,那么它的贡献是 \(0\)
好,我们设$$f(i)=\max_{d^2|i}d$$
那么, \(n\) 是不是完全平方数的倍数就可以表示为 \([f(n)=1]\)
代入关于 \(k\) 的式子,有:

\[k = \sum_{i=1}^n [f(i)=1] \]

现在进入推导式子,一波骚操作:
先套性质一,然后地球人都明白 \({d|f(i)} \Longleftrightarrow {d^2|i}\) , 再改为先枚举 \(d\) 的形式······然后就差不多了,康康下边

\[\begin{aligned} \sum_{i=1}^n [f(i)=1] &=\sum_{i=1}^n \sum_{d|f(i)} \mu(d) \\ &=\sum_{i=1}^n \sum_{d^2|i} \mu(d) \\ &=\sum_{d=1} \sum_{d^2|i} \mu(d) \\ &=\sum_{d=1} \mu(d) \sum_{d^2|i}1 \\ &=\sum_{d=1} \mu(d) \lfloor \frac n{d^2} \rfloor \end{aligned} \]

没了
显然,这是 \(O(\sqrt n)\) 的(\(\mu\) 提前筛出来)。
然后加上二分猜一个答案\(n\),用这种方法 \(check\) , 时间复杂度 \(O(\log n\sqrt n)\)
然后加个数论分块优化(不过优化效果不大) , 时间复杂度 \(O(\log n\sqrt{\sqrt n})\)

代码如下:

#include
#include
#include
using namespace std;
typedef long long LL;

const int N = 1e5;
int T , vis[N + 5] , prime[N + 5] , mu[N + 5] , tot;
LL k;

inline void getmu()
{
	mu[1] = 1 , vis[1] = 1;
	for(register int i = 2; i <= N; i++)
	{
		if (!vis[i]) prime[++tot] = i , mu[i] = -1;
		for(register int j = 1; j <= tot && prime[j] * i <= N; j++)
		{
			vis[prime[j] * i] = 1;
			if (i % prime[j] == 0) break;
			mu[prime[j] * i] = -mu[i];
		}
	}
	for(register int i = 1; i <= N; i++) mu[i] += mu[i - 1];
}

inline bool check(int m)
{
	int res = 0;
	LL j;
	for(register int i = 1; i * i <= m; i = j + 1)
	{
		j = floor(sqrt(m / (m / (i * i))));
		res += (LL)(m / (i * i)) * (mu[j] - mu[i - 1]);
	}
	return res >= k;
}

inline LL slove()
{
	LL l = k , r = k << 1 , res = 2e9 , mid;
	while (l <= r)
	{
		mid = (l + r) >> 1;
		if (check(mid)) res = min(res , mid) , r = mid - 1;
		else l = mid + 1;
	}
	return res;
}

int main()
{
	scanf("%d" , &T);
	getmu();
	while (T--)
	{
		scanf("%d" , &k);
		printf("%d\n" , slove());
	}
}