鸽巢原理 + floyd求最小环
题意非常直白,一共n个元素,如果 $a[i]$ AND $a[j] \ne 0$,则两个元素之间有边相连。求最小环。
然后只要$a[i]$和$a[j]$的二进制的某一位都为1,则$a[i]$就能和$a[j]$相连。
因为$a[i]$最大为$10^{18}$,所以二进制长度不超过64位
所以划分为64个集合,分别表示二进制第0位有1的元素,二进制第1位有1的元素, ....
二进制第63位有1的元素。
显然集合内的所有元素都是可以相连的。
当n>128的时候,根据鸽巢原理,必然存在一个集合内的元素大于等于3个,因为假如每个集合都只有两个的话,一共64个集合,就只有128个元素了。所以对于n>128的时候,最小环就是3。
而当n<=128的时候,n非常小,就可以跑floyd把最小环求出来了
注意题里有个坑,$a[i]$可能为0,0不在任何集合内,为了保证上面的蜂巢原理判断的正确性,首先需要先把为0的元素都给去掉。
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e5 + 10, INF = 1e7 + 10;
int n;
LL a[N];
int g[129][129], dis[129][129];
int floyd()
{
int ans = INF;
for (int k = 1; k <= n; k++)
{
for (int i = 1; i < k; i++)
for (int j = 1; j < i; j++)
{
// if (dis[i][j] == INF || g[i][k] == INF || g[k][j] == INF)
// continue;
ans = min(ans, dis[i][j] + g[i][k] + g[k][j]);
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
return ans != INF ? ans : -1;
}
int main()
{
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n;
// 过滤所有的0
for (int i = 1; i <= n; i++) {
LL x; // 注意输入的元素是long long类型
cin >> x;
if (x == 0)
n--, i--;
else
a[i] = x;
}
if (n > 128)
{
cout << 3;
return 0;
}
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++)
dis[i][j] = dis[j][i] = g[i][j] = g[j][i] = INF;
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++)
if (a[i] & a[j])
g[i][j] = g[j][i] = dis[i][j] = dis[j][i] = 1;
cout << floyd();
return 0;
}
法2 离散化+floyd求最小环
我自己做的方法。
其实和第一个方法的思路差不多,不过我自己做的时候没去想到拿蜂巢原理直接判断
同样考虑分为64个集合 定义 vector<int> bit[64]
,令bit[i]表示第i位为1的元素有哪些(存下标)
最后遍历这64个集合,
对于每个集合bit[i],如果该集合内元素大于等于3,则最小环为3,直接输出返回
否则将集合内的所有元素互相建边。因为一共64个集合,如果没直接返回,说明该集合元素小于等于2,所以64个集合总共最多128个元素。
因为集合内元素存的是下标,我这里直接把下标从1开始离散化了
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e5 + 10, INF = 1e7 + 10;
int n, m;
LL a[N];
int mp[N];
vector<int> bit[64]; // bit[i] 第i位为1的元素集合
int g[130][130], dis[130][130];
int floyd()
{
int ans = INF;
for (int k = 1; k <= m; k++)
{
for (int i = 1; i < k; i++)
for (int j = 1; j < i; j++)
ans = min(ans, dis[i][j] + g[i][k] + g[k][j]);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
return ans != INF ? ans : -1;
}
int main()
{
cin.tie(nullptr)->sync_with_stdio(false);
for (int i = 0; i < 130; i++)
for (int j = 0; j < 130; j++)
dis[i][j] = dis[j][i] = g[i][j] = g[j][i] = (i != j ? INF : 0);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
for (int j = 0; j < 64; j++)
{
if ((a[i] >> j) & 1)
bit[j].push_back(i);
}
}
for (int i = 0; i < 64; i++)
{
if (bit[i].empty())
continue;
if (bit[i].size() >= 3) // 如果存在某个集合元素大于等于3 则这个集合内随便选3个都可以形成环 那最少环数为3
{
cout << 3;
return 0;
}
else // 集合内的各个点可以连起来
{
for (int j = 0; j < bit[i].size(); j++)
{
for (int k = j + 1; k < bit[i].size(); k++)
{
int u = bit[i][j], v = bit[i][k];
if (mp[u] == 0)
mp[u] = ++m;
if (mp[v] == 0)
mp[v] = ++m;
u = mp[u], v = mp[v];
g[u][v] = g[v][u] = dis[u][v] = dis[v][u] = 1;
}
}
}
}
cout << floyd();
return 0;
}