就是给你一个长度为n的集合(保证没有重复的数出现),需要你去构成一个最大长度的子集,且这个子集里面的数都没有一种题目给定的关系,关系就是在这个子集中没有任何一个元素是另一个元素的素数倍数。比如a%b = 0, k = a b k = \frac{a}{b} k=bak不能是素数。{2,8,17} 是素数独立集,而 {2,8,16}或 {3,6} 不是素数独立集因为 16 8 = 2 , 6 3 = 2 \frac{16}{8} = 2,\frac{6}{3}=2 816=2,36=2
最开始想法非常简单就是对于一个数而言我就看这n个数当中有多少个是它的素数倍数然后n减去这些数的个数。
伪代码:
for(int i = 1; i <= n; i ++)
{
for(int j = arr[i]; j < N; j += arr[i])
{
if(j / arr[i] == primes) cnt++;
}
ans = max(ans, n-cnt);
}
当然肯定是不对的,就对于样例来说2 4 8 16 32,对于2这个数来说只有4不满足,意味着我可以把8 16 32和2放在一个集合,不用往后面说了。我是傻X。
思来想去最后还是看了题解,对于本题我将结合看到的题解和一些额外的知识理一下自己的思路。
首先我们要清楚什么是独立集:就是一个点集(也可以是其他的集合),点集中的各点没有关系。
那么就是点的个数最多的独立集。
那么对于最大独立集有一个很重要的性质就是:最大独立集 = 点的总数 - 点的最大匹配 (最小点覆盖) 其实这也很好去理解,对于本题的最大匹配也就是所有有关系的匹配,也就是存在素数倍数的两个数。
这里简单说明一下二分图和最大匹配吧,想了解详细点的话自行CSDN吧。
一张图中(一般都是无向图,但是对于一些有向图也会有二分图的性质),全部的点分为两个集合,对于每个集合中点与点之间是没有边连接的。那么所有的边存在两个集合之间的。
对于最大匹配来说,就是对于你找到的所有边,任意两条边没有公共顶点。
那么对于本题来说首先我们先来分析,什么情况下才有可能存在素数倍数的说法,如果对于两个数a, b如果a有奇数个质因子,b有偶数个质因子那么显然这两个数才会有可能会产生素数倍数的说法。因为一个素数因子数为奇数的数a只有乘上两个素数才能成为一个新的素数因子为奇数的数b,显然a与b不存在冲突关系,素数因子数为偶数的两个数同理。所以我们可以根据每个数的素数因子个数来划分集合,因为拥有相同奇偶性的数是不会有素数倍数的产生,这也是本题的关键所在。 具有相同奇偶性的数在一个集合。然后我们可以将有素数倍数的两个数建边。然后按照我们上面的讲述,我们建完二分图过后求一个最大匹配就完美解决了。但是通常我们求最大匹配的算法是匈牙利算法 ( O ( n ∗ e ) ) (O(n*e)) (O(n∗e))n个点e条边,对于本题来说很显然会超时的。那么我们这儿将会引用一种更好的算法 HK算法 O ( ( n ) ∗ e ) O(\sqrt(n)*e) O((n)∗e) 对于本算法笔者也是当一个黑盒使用。
#include
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr)
#define endl "\n"
#define xx first
#define yy second
using namespace std;
const int N = 5e4 + 5, M = 6e5+ 5;
const int inf = 0x3f3f3f3f;
struct Edge
{
int to, next;
}ns[M];
bool isnp[M], vst[N];;
int prime_num[M], prm[M];
int n, cur, top, dis;
int Mx[N], My[N], Nx, Ny, dx[N], dy[N], Ax[N], Ay[N];
int head[N], ext[M];
void cal_prime_number() // 求素数因子的个数
{
int res;
prime_num[1] = 0;
for (int i = 2; i < M; ++i)
{
int t = i;
res = 0;
for (int j = 2; j * j <= t; ++j)
{
while (t % j == 0)
{
++res;
t /= j;
}
}
if (t != 1) ++res;
prime_num[i] = res;
}
}
void get_prime(int u) // 求素数因子的种类
{
top = 0;
for (int i = 2; i * i <= u; ++i)
{
if (u % i == 0)
{
prm[top++] = i;
while (u % i == 0)
u /= i;
}
}
if (u != 1) prm[top++] = u;
}
bool searchP() //HK算法
{
queue<int> Q;
dis = inf;
memset(dx, -1, sizeof dx);
memset(dy, -1, sizeof dy);
for (int i = 1; i <= Nx; ++i)
{
if (Mx[i] == -1)
{
Q.push(i);
dx[i] = 0;
}
}
while (!Q.empty())
{
int u = Q.front();
Q.pop();
if (dx[u] > dis) break;
for (int i = head[u]; i != -1; i = ns[i].next)
{
int v = ns[i].to;
if (dy[v] == -1)
{
dy[v] = dx[u] + 1;
if (My[v] == -1) dis = dy[v];
else
{
dx[My[v]] = dy[v] + 1;
Q.push(My[v]);
}
}
}
}
return dis != inf;
}
bool DFS(int u)//HK算法
{
for (int i = head[u]; i != -1; i = ns[i].next)
{
int v = ns[i].to;
if (!vst[v] && dy[v] == dx[u] + 1)
{
vst[v] = 1;
if (My[v] != -1 && dy[v] == dis) continue;
if (My[v] == -1 || DFS(My[v]))
{
My[v] = u;
Mx[u] = v;
return true;
}
}
}
return false;
}
int Match()//HK算法
{
int res = 0;
memset(Mx, -1, sizeof Mx);
memset(My, -1, sizeof My);
while (true)
{
memset(vst, 0 , sizeof vst);
queue<int> Q;
dis = inf;
memset(dx, -1, sizeof dx);
memset(dy, -1, sizeof dy);
for (int i = 1; i <= Nx; ++i)
{
if (Mx[i] == -1)
{
Q.push(i);
dx[i] = 0;
}
}
while (!Q.empty())
{
int u = Q.front();
Q.pop();
if (dx[u] > dis) break;
for (int i = head[u]; i != -1; i = ns[i].next)
{
int v = ns[i].to;
if (dy[v] == -1)
{
dy[v] = dx[u] + 1;
if (My[v] == -1) dis = dy[v];
else
{
dx[My[v]] = dy[v] + 1;
Q.push(My[v]);
}
}
}
}
if (dis == inf) break;
for (int i = 1; i <= Nx; ++i)
{
if (Mx[i] == -1 && DFS(i))
++res;
}
}
return res;
}
void add_edge(int u, int v) //建边
{
ns[cur].next = head[u];
ns[cur].to = v;
head[u] = cur;
++cur;
}
void solve(int op)
{
cur = 0;
memset(head, -1, sizeof head);
memset(ext, 0, sizeof ext);
Nx = Ny = 0;
cin >> n;
for (int i = 1; i <= n; ++i)
{
int x;
cin >> x;
if (prime_num[x] & 1) //划分集合
{
Ax[++Nx] = x;
ext[x] = Nx;
}
else
{
Ay[++Ny] = x;
ext[x] = Ny;
}
}
for (int i = 1; i <= Nx; ++i)//建边
{
get_prime(Ax[i]);
for (int j = 0; j < top; ++j)
{
int goal = Ax[i] / prm[j];
int index = ext[goal];
if (index == 0) continue;
add_edge(i, index);
}
}
for (int i = 1; i <= Ny; ++i)//建边
{
get_prime(Ay[i]);
for (int j = 0; j < top; ++j)
{
int goal = Ay[i] / prm[j];
int index = ext[goal];
if (index == 0) continue;
add_edge(index, i);
}
}
cout << "Case " << op << ": " << n - Match() << endl;
}
signed main()
{
cal_prime_number();
int _;
cin >> _;
for(int i = 1; i <= _; i ++) solve(i);
return 0;
}