LightOJ1356 最大独立集 HK算法 素数分解
解析
当a和b满足\(a = b \times prime\)时,我们说a,b有冲突关系,将所有数看成是图中的顶点,那么a和b有冲突关系,就在a,b之间连一条边。题目是:给定一些数,从这些数中选出一些数组成一个集合,使这个集合中的每两个数都没有冲突关系,也就是每两个顶点之间都没有边,问这个集合最多可以有多少个数。
任意一个整数可以分解为多个素数因子的乘积,而只有分解后,素数因子数为奇数和偶数的两个数才有可能有冲突,因为一个素数因子数为奇数的数a只有乘上两个素数才能成为一个新的素数因子为奇数的数b,显然a与b不存在冲突关系,素数因子数为偶数的两个数同理。所以我们可以根据素数因子数的奇偶来将所有数划分为两个集合X和Y,X中的元素和Y中的元素可能有边,而同一个集合中的元素没有边,显然这是一个二分图。
再看这个问题,其实就是求最大独立集的元素个数。最大独立集就是选取最多的点组成一个集合,使任意所选两点均不相连。
有这么一个公式:最大独立集的元素个数 = 顶点数 - 最大匹配数。这里的最大匹配数就是二分图的最大匹配。所以我们只需要找出上述二分图的最大匹配数,用顶点数减去最大匹配数就是答案。注意二分图顶点的个数多达4e4,用匈牙利算法会严重超时,我们使用优化的算法——Hopcroft-Krap算法,邻接表存图时,时间复杂度是\(O(V\sqrt{E})\)。HK算法可以参考我的另一篇文章《求二分图最大匹配——Hopcroft-Krap算法》。
求最大独立集还有另一种求法,是求这个图的补图的最大团,但是这种方法时间复杂度很高,我们不采用这种方法解这道题
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e4 + 5;
const int maxm = 5e5 + 5;
const int inf = 0x3f3f3f3f;
struct Edge
{
int to, next;
};
bool isnp[maxm];
int prime_num[maxm];
int prm[maxm];
int n;
int Mx[maxn], My[maxn], Nx, Ny;
int dx[maxn], dy[maxn], dis;
bool vst[maxn];
int Ax[maxn], Ay[maxn];
int top;
int head[maxn];
Edge ns[maxm];
int ext[maxm];
int cur;
void cal_prime_number()
{
int res;
prime_num[1] = 0;
for (int i = 2; i < maxm; ++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()
{
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)
{
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 MaxMatch()
{
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;
}
int main()
{
cal_prime_number();
int t;
scanf("%d", &t);
int inp;
for (int cas = 1; cas <= t; ++cas)
{
cur = 0;
memset(head, -1, sizeof head);
memset(ext, 0, sizeof ext);
Nx = Ny = 0;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &inp);
if (prime_num[inp] & 1)
{
Ax[++Nx] = inp;
ext[inp] = Nx;
}
else
{
Ay[++Ny] = inp;
ext[inp] = 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);
}
}
printf("Case %d: %d\n", cas, n - MaxMatch());
}
return 0;
}
/*
3
5
2 4 8 16 32
5
2 3 4 6 9
3
1 2 3
*/