Description
给一些数,每个的因数个数不超过 $7$,求最少选出多少个,使得乘积为完全平方。无解输出 $-1$。
Solution
「每个的因数个数不超过 $7$」看上去非常玄学,它的本质是什么?
唯一分解定理:任何一个大于 $1$ 的自然数 $N$,可以唯一分解成有限个质数的乘积。即:
$$N = prod_{i=1}^{n} p_i ^{k_i}$$
这里 $forall 1 le i le n, k_i ge 1$,$p_1 < p_2 < cdots <p_n$,且 $forall 1le i le n, p_i in ext{prime} $,正确性显然。
约数个数定理:任何一个大于 $1$ 的自然数 $N$,它的约数个数为:
$$ d(N) = prod_{i=1}^{n}(k_i + 1) $$
显然对于每一种质因子,都有选 $0$ 个,选 $1$ 个……选 $k_i$ 个共 $(k_i + 1)$ 种选法,根据乘法原理,当然是它们的乘积。
好,我们用一下约数个数定理。设想 $a_i$ 有 $3$ 种质因数,那么 $d(a_i)$ 最小应当是 $(1+1)^3 = 8$,和 $d(a_i) le 7$ 矛盾,所以,$a_i$ 至多有 $2$ 种质因子。
所以,$a_i$ 要么是 $1$,要么可以表示为 $p_1^{k_1}$ 的形式,要么可以表示为 $p_1^{k_1} cdot p_2^{k_2}$ 的形式。不难发现把 $k$ 对 $2$ 取模不会对答案造成影响,这是因为一个数本身的平方因子可以直接相消了。
现在,$a_i$ 只会是 $1$,$p_1$,$p_1 cdot p_2$ 三种可能了。
- 如果一个 $a_i = 1$,直接输入 $1$,结束程序,因为直接选它就好了;
- 如果一个 $a_i = p_1$,那么,建一条边,把 $1$ 和 $p_1$ 连起来;
- 如果一个 $a_i = p_1cdot p_2$,那么,建一条边,把 $p_1$ 和 $p_2$ 连起来。
注意 $p$ 的值可能很大,需要离散化,可以在线性筛的时候预处理。
现在得到了一个图,答案就是这个图的 最小环 的大小。
为什么?选择一个数,就是选择一条边,如果我们选了一个环,那么环上的每个 $p_i$ 都有两条边相连,也就是乘了 $2$ 次,那么这当然是一个完全平方数了!
$10^6$ 以内质数大概是 $78500$ 个,这个数字记作 $P$。
用 Floyd 算法 $mathcal O(P^3)$ 求最小环是行不通的。
因为边权为 $1$,可以枚举起点然后 BFS,当然,这样直接做的复杂度是 $mathcal O(nP)$,当然也是行不通的。(枚举起点 $mathcal O(P)$,单次 BFS 是 $mathcal O(n)$,因为有 $n$ 条边)
深入剖析,发现 一个环内必然有一个点 $le sqrt{max a_i}$,这是因为不会有两个大于 $sqrt{max a_i}$ 的点之间有连边,只要这个较小数作为起点被枚举了,那么这个环就必然会被 BFS 到。所以,起点只要枚举到 $sqrt{max a_i} = 10^3$ 即可。
当然,必然点都是质数,所以我们记 $10^3$ 以内的质数个数为 $P'$,这个算法的时间复杂度就是 $mathcal O(nP')$,可以通过。
当然我的代码偷个懒是直接枚举到 $sqrt{max a_i} = 10^3$ 的……
#include <bits/stdc++.h> #define REP(i, x, y) for(register int i = x; i <= y; i++) using namespace std; const int N = 1e5 + 5, A = 1e6 + 5, SQRTA = 1000; const int INF = 0x3f3f3f3f; int n, ncnt, hd, tl, que[N][2], ans = INF; int a[N], prm[N], id[A], h[N], dis[N]; bool npr[A]; struct edge { int v, nxt; } e[A << 1]; void EulerSieve() { for(int i = 2; i < A; i++) { if(!npr[i]) prm[++prm[0]] = i, id[i] = prm[0] + 1; for(int j = 1; j <= prm[0] && i * prm[j] < A; j++) { npr[i * prm[j]] = true; if(i % prm[j] == 0) break; } } } inline void AddEdge(int u, int v) { e[++ncnt] = (edge){v, h[u]}; h[u] = ncnt; e[++ncnt] = (edge){u, h[v]}; h[v] = ncnt; } void Divide(int x) { int p[4] = {0}, k[4] = {0}; for(int i = 1; i <= prm[0] && prm[i] * prm[i] <= x; i++) { if(x % prm[i] == 0) { p[++p[0]] = prm[i]; while(x % prm[i] == 0) k[p[0]] ^= 1, x /= prm[i]; if(!k[p[0]]) p[0]--; } } if(x > 1) p[++p[0]] = x, k[p[0]] = 1; if(!p[0]) { cout << 1 << endl; exit(0); } else if(p[0] == 1) AddEdge(1, id[p[1]]); else AddEdge(id[p[1]], id[p[2]]); } void Bfs(int s) { memset(dis, 0x3f, sizeof dis); dis[s] = 0; que[1][0] = s; que[1][1] = 0; hd = tl = 1; while(hd <= tl) { int u = que[hd][0], fa = que[hd][1]; hd++; for(int i = h[u]; i; i = e[i].nxt) { int v = e[i].v; if(v == fa) continue; if(dis[v] == INF) { tl++; que[tl][0] = v; que[tl][1] = u; dis[v] = dis[u] + 1; } else ans = min(ans, dis[u] + dis[v] + 1); } } } int main() { cin >> n; REP(i, 1, n) cin >> a[i]; EulerSieve(); REP(i, 1, n) Divide(a[i]); REP(i, 1, SQRTA) Bfs(i); if(ans == INF) cout << -1 << endl; else cout << ans << endl; return 0; }