回归!!!
一、典 给定若干集合,问从包含 1 的集合出发,最少经过多少次“跳转到有公共元素的集合”可以到达包含 m 的集合(虚拟点建图 / 二分图)
题目链接:F - Merge Set
思路:一开始无法直接入手,既然是给了很多个数组,考虑建图解决。直接建图是 O (n ^ 2),考虑虚拟点建图法(ai说的,其实本质就是二分图),用 1 ~ n表示数组集合,因为拥有相同的数字就可以合并,将数字作为中转点,用 n + 1 ~ n + m表示数字,建图就只有 n + m 个顶点,通过 bfs解决,时间复杂度 O(n + m),二分图理解为左边图是集合 1 ~ n,右边图为数字中转站 n + 1 ~ n + m
代码:
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
vector<int> e[400010]; // 邻接表,存储图的边
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m; // 输入集合个数 n 和元素最大编号 m
// 建图:集合编号为 1~n,元素编号对应为 n+1~n+m
for (int i = 1; i <= n; i++)
{
int o;
cin >> o; // 当前集合包含的元素个数
for (int j = 1; j <= o; j++)
{
int u;
cin >> u; // 元素编号
// 集合 i 和元素 u(映射到 u+n)之间建立双向边
e[i].push_back(u + n);
e[u + n].push_back(i);
}
}
vector<int> v(n + 5); // 标记集合节点是否访问过(0 未访问,1 已访问)
vector<int> d(m + n + 5, -1); // 记录距离,初始化为-1表示未访问
queue<int> q;
q.push(n + 1); // 起点是元素编号为 1 的节点,对应图中的节点编号 n+1
d[n + 1] = 0; // 起点距离设为 0
while (q.size())
{
int u = q.front();
q.pop();
for (auto i : e[u]) // 遍历与节点 u 相邻的所有集合节点 i
{
if (v[i]) // 集合 i 已访问则跳过
continue;
v[i] = 1; // 标记集合 i 已访问
for (auto j : e[i]) // 遍历集合 i 中包含的所有元素 j
{
if (d[j] == -1) // 元素 j 未访问
{
d[j] = d[u] + 1; // 记录距离(从 u 到 j 距离加1)
q.push(j); // 入队继续 BFS
}
}
}
}
if (d[m + n] == -1) // 如果目标元素 m (编号 n+m) 距离为-1,表示不可达
{
cout << d[n + m]; // 输出 -1
return 0; // 结束程序
}
// 因为 BFS 距离是元素节点间跳跃的次数(包含了集合与元素之间的边),
// 需要减1以符合题意的步数(只计算集合间跳转次数)
cout << d[m + n] - 1;
}
总结
atcoder 的典题确实牛,每次补题都能学到很多知识点,比如这种建图方法,还是要多补补啊,这几天有点小事情忙(摆,真成fvv了),好几天没更,后面会接着更,放假真好,天天玩,学了个寂寞
明天定个小目标吧:vp 一把 edu 和一把 ABC 的 DEF ,学一下树形dp