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;
}
posted @ 2020-03-17 23:50  syksykCCC  阅读(343)  评论(2编辑  收藏  举报