Codeforces 1325E Ehab's REAL Number Theory Problem
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 1\le i \le n, p_i \in \text{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_1\cdot 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;
}

浙公网安备 33010602011771号