『素瘤算法系列10』魔法石(tarjan·边双连通分量)

P r o b l e m \mathrm{Problem} Problem

幻象群岛是由 n n n个孤立的岛屿构成。岛屿之间有一些残破的石桥,而桥心的石墩上,就有可能镶嵌着上古魔法石。约翰尼可以通过这些石桥,从一座岛跑到另一座岛,如果岛上恰好有魔法石,他就可以顺便收集。但是由于这些石桥实在是太残破了,约翰尼经过之后,石桥就会崩塌,不能再次通过。(由于约翰尼踩过的部分很快就会崩塌,所以他也不能先跑到桥心,然后原路返回)。

约翰尼现在处在岛a,而岛b上则有一个传送门,只有在那里,约翰尼才能安全地离开幻象群岛。约翰尼想知道,他能顺利地收集到至少一块上古魔法石,并安全离开吗?

S o l u t i o n \mathrm{Solution} Solution

前置芝士:

  • 边双连通分量:该分量内任意两点都存在两条边不想交的路径。
  • 点双连通分量:该分量内任意两点都存在两条点不相交的路径。

我们知道边双的求法可以直接使用割边来分割,即如果一条边是割边,那么它一定不属于任意一个边双内;而且,一个边双内也不存在割边。

回归本题:
联系变双,求 s s s t t t是否存在一条路径,使得一条边的边权为 1 1 1,可以转化为:

  • 添加边 ( s , t , 0 ) (s,t,0) (s,t,0)的情况下是否存在两条从 s s s t t t的两条不相交的路径。
  • 相当于问你 s s s t t t是否位于一个边双内而且边双内是否存在边权为1的边。

最后我们只需要用并查集判断即可

#include <bits/stdc++.h>

using namespace std;
const int N = 3e5 + 10;

int n, m, cnt = 0, tot = 1;
int Link[N], low[N], dfn[N], Bri[N], fa[N], R[N], vis[N];
struct node {
	int x, y, next, v;
} e[N * 2];

void Clear(void)
{
	memset(R,0,sizeof R);
	memset(vis,0,sizeof vis);
	memset(low,0,sizeof low);
	memset(dfn,0,sizeof dfn);
	memset(Bri,0,sizeof Bri);
	memset(Link,0,sizeof Link);
	cnt = 0; tot = 1;
	return;
}

int read(void)
{
	int s = 0, w = 0; char c = getchar();
	while (c < '0' || c > '9') w |= c == '-', c = getchar();
	while (c >= '0' && c <= '9') s = s*10+c-48, c = getchar();
	return w ? -s : s;
}

int get(int x) {
	if (fa[x] == x) return x;
	return fa[x] = get(fa[x]);
}

void tarjan(int x,int last)
{
	dfn[x] = low[x] = ++ cnt;
	for (int i=Link[x];i;i=e[i].next)
	{
		int y = e[i].y, v = e[i].v;
		if (dfn[y] == 0) {
			tarjan(y,i);
			low[x] = min(low[x],low[y]);
			if (dfn[x] < low[y]) Bri[i] = Bri[i^1] = 1;
		}
		else if (i != (last ^ 1))
			low[x] = min(low[x],dfn[y]);
	}
	return;
}

void add(int x,int y,int v) {
	e[++tot] = {x,y,Link[x],v};
	Link[x] = tot;
}

void work(void)
{
	n = read(), m = read();
	for (int i=1;i<=m;++i)
	{
		int x = read(), y = read(), v = read();
		add(x,y,v), add(y,x,v);
	}
	int S = read(), T = read();
	add(S,T,0);
	add(T,S,0);
	for (int i=1;i<=n;++i)
		if (dfn[i] == 0) tarjan(i,0);
	for (int i=1;i<=n;++i) fa[i] = i;
	for (int i=2;i<=tot;i+=2) 
		if (Bri[i] == 0) {
			int x = e[i].x;
			int y = e[i].y;
			int v = e[i].v; 
			fa[get(x)] = get(y);
			if (v == 1) vis[x] = vis[y] = 1;
		}
	for (int i=1;i<=n;++i) R[get(i)] |= vis[i];
	if (get(S) == get(T) and R[get(S)]) puts("YES");
	else puts("NO");
	Clear();
	return;
}

int main(void)
{
	int T = read();
	while (T --) work();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要获取无向图的双连通分量,可以使用Tarjan算法。以下是一个Python实现的示例代码: ``` def tarjan_biconnected_components(graph): """ Tarjan算法计算无向图的双连通分量 :param graph: 无向图,用邻接列表表示 :return: 双连通分量列表 """ index_counter = [0] stack = [] lowlink = {} index = {} result = [] bridges = [] def strongconnect(node): # 为节点赋予唯一的索引 index[node] = index_counter[0] lowlink[node] = index_counter[0] index_counter[0] += 1 stack.append(node) # 对于每个相邻节点v for v in graph[node]: # 如果v没有被访问过,则递归调用strongconnect if v not in index: strongconnect(v) lowlink[node] = min(lowlink[node], lowlink[v]) # 如果v是一个桥,则将桥添加到bridges列表中 if lowlink[v] == index[v]: bridges.append((node, v)) # 如果v已经在堆栈中,则更新此节点的lowlink elif v in stack: lowlink[node] = min(lowlink[node], index[v]) # 如果节点是一个连接分量的根,则弹出堆栈,并收集连通分量 if lowlink[node] == index[node]: connected_component = [] while True: v = stack.pop() connected_component.append(v) if v == node: break result.append(connected_component) for node in graph: if node not in index: strongconnect(node) return result ``` 使用示例: ``` graph = { 1: {2, 3}, 2: {1, 3, 4}, 3: {1, 2, 4}, 4: {2, 3, 5}, 5: {4} } result = tarjan_biconnected_components(graph) print(result) # 输出:[[1, 2, 3], [4, 5]] ``` 以上代码实现了Tarjan算法,用于计算无向图的双连通分量。传入的图以邻接列表表示,返回的结果是双连通分量的列表。对于结果中的每个双连通分量,其包含的节点组成了一个强连通分量,即任意两个节点都有一条路径相连。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值