[Nowcoder] network | Tarjan 边双连通分量 | 缩点 | LCA倍增优化 | 并查集

Posted PushyTao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Nowcoder] network | Tarjan 边双连通分量 | 缩点 | LCA倍增优化 | 并查集相关的知识,希望对你有一定的参考价值。

题目链接

题目描述

A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of computers are connected directly or indirectly by successive links, so data can be transformed between any two computers. The administrator finds that some links are vital to the network, because failure of any one of them can cause that data can’t be transformed between some computers. He call such a link a bridge. He is planning to add some new links one by one to eliminate all bridges.

You are to help the administrator by reporting the number of bridges in the network after each new link is added.

输入描述:

The input consists of multiple test cases. Each test case starts with a line containing two integers N ( 1 ≤ N ≤ 100 , 000 ) (1 \\leq N \\leq 100,000) (1N100,000) and M ( N − 1 ≤ M ≤ 200 , 000 ) (N - 1 \\leq M \\leq 200,000) (N1M200,000).
Each of the following M lines contains two integers A and B ( 1 ≤ A ≠ B ≤ N 1\\leq A \\not= B \\leq N 1A=BN), which indicates a link between computer A and B. Computers are numbered from 1 to N. It is guaranteed that any two computers are connected in the initial network.
The next line contains a single integer Q Q Q ( 1 ≤ Q ≤ 1 , 000 1 \\leq Q \\leq 1,000 1Q1,000), which is the number of new links the administrator plans to add to the network one by one.
The i-th line of the following Q lines contains two integer A and B ( 1 ≤ A ≠ B ≤ N ) (1 \\leq A \\not= B \\leq N) (1A=BN), which is the i-th added new link connecting computer A and B.
The last test case is followed by a line containing two zeros.

输出描述:

For each test case, print a line containing the test case number( beginning with 1) and Q lines, the i-th of which contains a integer indicating the number of bridges in the network after the first i new links are added. Print a blank line after the output for each test case.

示例1

输入

3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0

输出

Case 1:
1
0

Case 2:
2
0

首先纪念下这道题:

过题的过程比较艰辛(太菜了)

题意:

给出一个n个点n条边的图,有q次操作加询问,每次加入一条边,并询问加入这一条边之后的图中的桥的数量

思路:

偏向总结向,需要完美思路自行跳转下方
wa 30%的做法:
这道题目中,遇到重边不可能是桥,所以说在建立边的时候,用 m a p map map标记 p a i r < i n t , i n t > pair<int,int> pair<int,int>,来维护那些便被标记,然后每次暴力跑 T a r j a n Tarjan Tarjan[这里就注定了无法ac],统计桥的数量,如果说加入的边是被标记过的,那么说这条边就不再是桥,就将答案-1,看上去似乎是可行的,其实不对,👇
wa_code
tle 80%的做法:
在跑 T a r j a n Tarjan Tarjan的过程中就应该在 l o w [ t o ] > d f n [ u ] low[to] > dfn[u] low[to]>dfn[u]这个条件的基础上加入另一个条件:当前边是不是重边,重边肯定不是桥
所以这样就不用每次判断加入的边是不于是重边
这里过了大部分的数据,但是还是太年轻了,加入的边影响的可能不仅仅是小规模的图中的较为相邻的节点,有时候加入的边会影响很多个部分,而且只是适用于数据范围较小的情况,👇
tle_code
ac 100%的做法:
我们应该清楚地了解过一个叫做强连通分量的概念:
[1] [https://blog.csdn.net/weixin_43843835/article/details/88381828]
如果一个有向图中,存在一条回路,所有的结点至少被经过一次,这样的图为强连通图。在强连通图的基础上加入一些点和路径,使得当前的图不在强连通,称原来的强连通的部分为强连通分量。
在这里我们求出边双连通分量:不存在割边的强连通分量成为边双连通分量

图片来源
因为图中存在桥,所以说我们直接求出边双连通分量并统计出数量在这里我们记作 c n t S C C cntSCC cntSCC,开始的时候桥的数量是 c n t S C C − 1 cntSCC-1 cntSCC1,并且桥的数量就是缩点之后边的数量。对于一个边双联通分量,我们将其看成一个点,那么这整张图就变成了一棵树,所以说就把图上的操作变成了树上的操作

对于每一次的操作,我们可以很清楚的了解到:

  1. 对于加入的一条边如果说这条边的两个端点在同一个边双联通分量里面,就不会对答案产生影响,那么说我们就不需要考虑对加入的这条边进行操作
  2. 如果说加入的这条边不在同一个边双联通分量里面,那么就可以说加入这条边之后,两点所在块之间的最短路径上的所有边都不再是桥,所以说就可以用 l c a lca lca来进行操作,两个端点向祖先点跳,并标记沿途的边[考虑优化]
    ac_code
#define lowbit(x) (x & (-x))
#define Clear(x,val) memset(x,val,sizeof x)
int n, m;
struct node {
	int to, nex;
} e[maxn << 2], e2[maxn << 2];
int cnt, head[maxn], dfc, hd[maxn], idx;
int dfn[maxn], low[maxn], pos[maxn];
int edge[maxn << 2];
int depth[maxn];
int cntSCC, ans;
void init() {
	idx = cnt = dfc = cntSCC = 0;
	for(int i = 1; i <= n; i++) {
		head[i] = -1;
		hd[i] = -1;
		pos[i] = 0;
		depth[i] = 0;
	}
//	memset(edge, 0, sizeof edge);/// set all bridge = 0
}
void add(int u, int v) {
	e[cnt].to = v;
	e[cnt].nex = head[u];
	head[u] = cnt ++;
}
void add2(int u,int v) {
	e2[idx].to = v;
	e2[idx].nex = hd[u];
	hd[u] = idx ++;
}
stack<int> stk;
void Tarjan(int u, int father) {
	stk.push(u);
	dfn[u] = low[u] = ++ dfc;
	for(int i = head[u]; ~i; i = e[i].nex) {
		int to = e[i].to;
		if(!dfn[to]) {
			Tarjan(to, u);
			low[u] = min(low[u], low[to]);
			if(low[to] > dfn[u]) edge[i] = edge[i ^ 1] = 1;/// is a brage
		} else if(to != father) {
			low[u] = min(low[u], dfn[to]);
		}
	}
	if(low[u] == dfn[u]) {
		pos[u] = ++ cntSCC;
		while(stk.top() != u) {
			pos[stk.top()] = cntSCC;
			stk.pop();
		}
		stk.pop();/// pop the point u
	}
}
int bz[maxn][20];
int fa[maxn];
void initfa() {
	for(int i=1; i<=n; i++) fa[i] = i;
}
int findfa(int x) {
	if(x == fa[x]) return x;
	else return fa[x] = findfa(fa[x]);
}
void _union(int u,int v) {
	int fau = findfa(u);
	int fav = findfa(v);
	if(fau == fav) return ;
	fa[fau] = fav;
}
void dfs(int u,int fat) {
	bz[u][0] = fat;
	depth[u] = depth[fat] + 1;
	for(int i=1; i<=19; i++) {
		bz[u][i] = bz[bz[u][i-1]][i-1];
	}
	for(int i=hd[u]; ~i; i=e2[i].nex) {
		int to = e2[i].to;
		if(to == fat) continue;
		dfs(to,u);
	}
}
int lca(int u,int v) {
	if(depth[u] < depth[v]) swap(u,v);
	for(int i=19; i>=0; i--) {
		if(depth[bz[u][i]] >= depth[v]) {
			u = bz[u][i];
		}
	}
	if(u == v) return v;
	for(int i=19; i>=0; i--) {
		if(bz[u][i] != bz[v][i]) {
			u = bz[u][i];
			v = bz[v][i];
		}
	}
	return bz[u][0];
}
int main() {
	int _ = 0;
	while(cin >> n >> m && (n || m)) {
		init();//
		memset(dfn,0,sizeof dfn);
		memset(low,0,sizeof low);
		memset(bz,0,sizeof bz);
		memset(edge,0,sizeof edge);
		for(int i = 1; i <= m; i++) {
			int u = read, v = read;
			add(u, v);
			add(v, u);
		}
		while(stk.size()) stk.pop();
		for(int i=1; i<=n; i++) {
			if(!dfn[i]) Tarjan(i, 0);
		}
//		puts("Tarjan _ ok");
		for(int i=1; i<=n; i++) {
			int u = i;
			for(int j=head[u]; ~j; j=e[j].nex) {
				int to = e[j].to;
				if(pos[i] == pos[to]) continue;
				add2(pos[i],pos[to]);
			}
		}
		dfs(1,0);///lca
		ans = cntSCC - 1;
		initfa();
		int q = read;
		printf("Case %d:\\n", ++_);
		while(q --) {
			int u = read, v = read;
			if(pos[u] == pos[v]) printf("%d\\n",ans);
			else {
				u = pos[u];
				v = pos[v];/// which scc are the u&&v in
				int _lca = lca(u,v);
				int fau = findfa(u);
				while(depth[_lca] < depth[fau]){
					fa[fau] = bz[fau][0];
					-- ans;
					fau = findfa(fau);
//					puts("bug???");
				}
				int fav = findfa(v);
				while(depth[_lca] < depth[fav]){
					fa[fav] = bz[fav][0];
					-- ans;
					fav = findfa(fav);
				}
				printf("%d\\n",ans);
			}
		}
		puts("");
	}
	return 0;
}
/**
3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0

**/

以上是关于[Nowcoder] network | Tarjan 边双连通分量 | 缩点 | LCA倍增优化 | 并查集的主要内容,如果未能解决你的问题,请参考以下文章

LCA(最近公共祖先)算法

[强连通分量+Tarjan缩点]

NowCoder数列

[nowcoder]最长区间

Nowcoder84D

Nowcoder 提高组练习赛-R1