CRB and Graph

Description

一个连通,n 个顶点和 m 条边的无向图被给了 CRB。

一对顶点 ( u , v ) (u,v) (u,v)(u 认证机构的任务是寻找每条边的关键对。帮助他!)

Input

有多个测试案例。输入的第一行包含一个整数 t,表示测试用例的数目。

对于每一个测试案例:

第一行包含两个整数 n,m 表示顶点数和边数。

接下来的 m 行包含一个整数对 a 和 b,表示 a 和 b 之间一个无向边。

1 ≤ t ≤ 12 1≤t≤12 1t12

1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^ 5 1n,m105

1 ≤ a , b ≤ n 1≤a,b≤n 1a,bn

所有给定的图连接。

没有重边和自循环,图是简单的。

Output

对于每一条边, 若删除该边后存在两点不可达,则输出这两个点, 如果存在多个则输出u尽可能大,如果还存在多个则v尽可能小的。 不存在输出0 0。

Sample Input
2
3 2
3 1
2 3
3 3
1 2
2 3
3 1
Sample Output
1 2
2 3
0 0
0 0
0 0

题目分析

去掉一条边,问去掉之后不能互通的两个点的编号(u 尽量大,v 尽量小)。

我们先用 Tarjan 找割边。

首先很明显,一条非割边的边在去掉之后,不会对其他点产生影响,所以输出0 0

但若该边是割边,去掉之后会将图分成两个连通块,我们要的答案就分别在这两个连通块中。

首先,n 所在的连通块中是我们要的点 v

因为 n 不能为点 u,否则 v 就不再是合法节点了。

其次,为了满足题目对答案的要求,易得 v ← u + 1 v \gets u+1 vu+1

综上我们只需要求出 n 不在的连通块的最大节点,就可以输出答案了。

这时候我们用 dfs 从 n 去遍历每一个节点,记录它的子节点最大编号以及它的深度

最后遍历每一条输入过的边,如果 d e p u i > d e p v i dep_{u_i} > dep_{v_i} depui>depvi,就证明 v a l u i ≤ v a l v i val_{u_i} ≤ val_{v_i} valuivalvi(val 存一个节点的最大儿子编号),那就输出 v a l u i val_{u_i} valui, 然后再输出 v a l u i + 1 val_{u_i}+1 valui+1

反之道理一样。

代码实现

在双向边建边的部分这道题要提一下。

因为我们要在输出结果的时候使用开始从 1 循环到 m 输入的边的两个顶点,

所以,我们 vis 数组对边的编号的存储要和输入的一样,还要满足都能从两个有向边编号指向这个边。

这时我们就需要在赋值判桥数组的时候对编号进行改变。

我们 v i s i + 1 > > 1 ← 1 vis_{i+1>>1} \gets 1 visi+1>>11,这样我们就可以从 1 到 m 访问边了。

#include<bits/stdc++.h>
using namespace std;//边双

const int maxn = 1000005;
int n, m;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2];
int low[maxn], dfn[maxn]; 
int co[maxn], reco[maxn], recoa[maxn];
int st[maxn], vis[maxn];
int tmp, col, top;
int root;
int u[maxn], v[maxn];
int dep[maxn], val[maxn];

void init ()
{
	cnt = tmp = col = top = 0;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	memset (dfn, 0, sizeof dfn);
	memset (low, 0, sizeof low);
	memset (vis, 0, sizeof vis);
	memset (co, 0, sizeof co);
	memset (reco, 0x7f, sizeof reco);
	memset (recoa, 0, sizeof recoa);
	memset (u, 0, sizeof u);
	memset (v, 0, sizeof v);
	memset (dep, 0, sizeof dep);
	memset (val, 0, sizeof val);
}

int read ()
{
	int x = 1, s = 0;
	char ch = getchar ();
	while (ch < '0' or ch > '9') {if (ch == '-') x = -1; ch = getchar ();}
	while (ch >= '0' and ch <= '9') {s = s * 10 + ch - '0'; ch = getchar ();}
	return x * s;
}

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

int find (int x)
{
	if (x % 2 == 0) return x - 1;
	return x + 1;
}

void tarjan (int u, int fa)
{
	dfn[u] = low[u] = ++tmp;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == fa) continue;	
		if (!dfn[v])
		{
			tarjan (v, u);
			low[u] = min (low[u], low[v]);
			if (dfn[u] < low[v])
			{
				vis[i + 1 >> 1] = 1;
			}
		}
		else low[u] = min (low[u], dfn[v]);
	}

}


void dfs (int u, int d)
{
	dep[u] = d;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (dep[v]) continue;
		dfs (v, d + 1);
		val[u] = max (val[u], val[v]);
	}
}

int main ()
{
	int T;
	T = read ();
	while (T--)
	{
		init ();
//		memset (reco, 0x7f, sizeof reco);
		n = read (), m = read ();
		for (int i = 1; i <= m; i++)
		{
			u[i] = read (), v[i] = read ();
			add (u[i], v[i]), add (v[i], u[i]);
		}
		for (int i = 1; i <= n; i++) val[i] = i;
		dfs (n, 1);
		for (int i = 1; i <= n; i++)
		{
			if (!dfn[i])
			{
				root = i;
				tarjan (i, -1);
			}
		}
		for (int i = 1; i <= m; i++)
		{
			if (!vis[i]) {printf ("0 0\n");continue;}
//			printf ("%d %d\n", reco[co[u[i]]], recoa[co[v[i]]]);
			if (dep[u[i]] > dep[v[i]]) printf ("%d %d\n", val[u[i]], val[u[i]] + 1);
			else printf ("%d %d\n", val[v[i]], val[v[i]] + 1);
		}
	}
	return 0;
}

—— E n d End End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值