小白暑假加训备战网络赛day13

回归!!!


一、典 给定若干集合,问从包含 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值