算法刷题记录(Day 26)

本文解析了两道编程题目,POJ 2492 Abug'sLife利用并查集检测性别异常,SPF(POJ 1523)通过tarjan算法寻找割点。讲解了如何运用tarjan算法改进,以及如何在无向图中正确计算割点和连通分量。
摘要由CSDN通过智能技术生成

A Bug’s Life(poj 2492)

原题链接
题目类型:并查集
解题思路:若存在a、b则代表a和b不位于同一个性别中,因此对于每一个点,建立其对立点a’。当出现a,b时,则代表a和b’ 、a’和b在同一个集合中。最终通过查看a和a’是否在同一个集合中,来判断是否存在异常。

#include<iostream>
using namespace std;
#define NMAX 2200
int num = 1;
int T;
int fa[NMAX * 2];
int find(int x) {
	if (x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
int main() {
	cin >> T;
	while (T--) {
		int n, m;
		cin >> n >> m;
		for (int i = 1; i <= 2 * n; i++)  fa[i] = i;

		for (int i = 0; i < m; i++) {
			int a, b;
			cin >> a >> b;
			int A1 = find(a), B2 = find(b + n);
			fa[A1] = B2;   //注意在每一次对代表元进行操作的时候都应该先进行代表元的查找
			int B1 = find(b),A2 = find(a + n);
			fa[A2] = B1;
		}
		//for (int i = 1; i <= n; i++) cout << fa[i] << " " << fa[i + n] << endl;
		int key = 0;
		for (int i = 1; i <= n; i++) {
			if (find(i) == find(i + n)) {
				key = 1;
			}
		}
		cout << "Scenario #" << num << ":" << endl;
		if (key) cout << "Suspicious bugs found!" << endl << endl;
		else cout << "No suspicious bugs found!" << endl << endl;
		num++;
	}
}

tip;
1.presentation error往往是由于输出格式的错误导致的。
2.OLE错误往往是由于输出了不应该输出的信息造成的。

SPF(poj 1523)

原题链接
题目类型:割点
使用tarjan求割点

如果 p 存在一个子结点 q 满足 low[q]>=dfn[p],说明 q 无法通过它的子树“逃”到比 q 的dfs序更小的节点。那么,既然走子树走不通, q 如果想到达这样的点,只能选择经过它的父节点 p。因此,如果删去 p , q 和dfs序小于p 点的点就分开了。
(注意节点的编号和dfs序之间的关系)

由于上述方法仅仅适用于判断某个点是否为割点,而对于联通分量的计算是错误的,因此需要进行改进。
需要注意的是,为了正确计算删去每一个点后所剩下的连通分量数,应该对于每一个点进行一次dfs。

#include<iostream>
#include<vector>
#include<stack>
#define NMAX 1100
using namespace std;
struct edge {
	int from, to;
	int next;
};
vector<edge > E;
vector<pair<int, int> > res;
stack<int > S;

int h[NMAX];
int a, b;
int n;
int dfn[NMAX], low[NMAX], inq[NMAX];
int tot = 0;

void addline(int x, int y) {
	edge cur;
	cur.from = x, cur.to = y, cur.next = h[x];
	h[x] = E.size();
	E.push_back(cur);
	cur.from = y, cur.to = x, cur.next = h[y];
	h[y] = E.size();
	E.push_back(cur);
}

void tarjan(int x,int key) {
	int ret = 0;
	dfn[x] = low[x] = ++tot;
	inq[x] = 1;
	for (int i = h[x]; i != 0; i = E[i].next) {
		int to = E[i].to;
		if (!dfn[to]) {
			tarjan(to, 0);
			low[x] = min(low[x], low[to]);
			if (low[to] >= dfn[x]) ret++;
		}
		else if (inq[to]) low[x] = min(low[x], dfn[to]);
	}
	//仅当其为搜索树的根节点时,才予以计数
	if (key && ret > 1) res.push_back(make_pair(x, ret));
	
}
int main() {
	cin >> a;
	int num = 0;
	while (a) {
		//清空所有的变量
		memset(h, 0, sizeof(h));
		memset(inq, 0, sizeof(inq));
		memset(dfn, 0, sizeof(dfn));
		res.clear();
		while (!S.empty()) S.pop();
		tot = 0;

		cin >> b;
		addline(a, b);
		cin >> a;
		while (a) {
			cin >> b;
			if (a > n) n = a;
			if (b > n) n = b;
			addline(a, b);
			cin >> a;
		}

		
		for (int i = 1; i <= n; i++) {
		//每一个点都应该做一次tarjan算法
			memset(inq, 0, sizeof(inq));
			memset(dfn, 0, sizeof(dfn));
			if (!dfn[i]) tarjan(i,1);
		}
		num++;
		cout << "Network #" << num << endl;
		if (!res.size()) cout << "  No SPF nodes" << endl;
		else {
			for (int i = 0; i < res.size(); i++) cout << "  SPF node " << res[i].first << " leaves " << res[i].second << " subnets" << endl;
		}
		cout << endl;
		
		cin >> a;
	}
}

在对于tarjan求割点和桥进行进一步的学习后,发现昨日自己的理解出现了问题,因此做出了如下改进。

tarjan求解割点和桥视频讲解

一些思路的更正

首先,割点和桥是无向连通图中的概念,而最开始使用tarjan来求取有向图的强连通分量,此时有向图的连通性是待求解的。
其次,根的概念是指dfs搜索过程的起点。
然后,对于节点from,其子节点to(注意是dfs搜索过程中的子节点),若low[to]>=dfn[from],则代表to只有通过from才能到达比from搜索序更小的节点,因此断掉边(from,to),会切断from的祖先联通分量和以to为根节点的子连通分量。从上述论述中不难发现,当from的子连通分量有n个,那么再加上祖先联通分量,总共n+1个。
最后需要注意的,由于是无向图的特性,因此to一定是可达from的,但是此时我们不能使用from的dfn来更新to的low值,因此在tarjan过程中需要传入其父节点的编号。

#include<iostream>
#include<vector>
#include<stack>
#include<set>
#define NMAX 1100
using namespace std;
struct edge {
	int from, to;
	int next;
};
vector<edge > E;
set<pair<int, int> > res;
stack<int > S;

int h[NMAX];
int a, b;
int n;
int dfn[NMAX], low[NMAX], inq[NMAX];
int tot = 0;
set<int> dot;
void addline(int x, int y) {
	edge cur;
	cur.from = x, cur.to = y, cur.next = h[x];
	h[x] = E.size();
	E.push_back(cur);
	cur.from = y, cur.to = x, cur.next = h[y];
	h[y] = E.size();
	E.push_back(cur);
}

void tarjan(int x,int fa,int key) {
	int ret = 0;
	dfn[x] = low[x] = ++tot;
	inq[x] = 1;
	for (int i = h[x]; i != -1; i = E[i].next) {
		int to = E[i].to;
		if (!dfn[to]) {
			tarjan(to, x,0);
			low[x] = min(low[x], low[to]);
			if (low[to] >= dfn[x]) ret++;
		}
		else if (inq[to] && to!=fa) low[x] = min(low[x], dfn[to]);
	}
	if (key && ret > 1) res.insert(make_pair(x, ret));
	else if (!key && ret > 0) res.insert(make_pair(x, ret +1 ));
}
int main() {
	cin >> a;
	int num = 0;
	while (a) {
		//清空所有的变量
		memset(h, -1, sizeof(h)); //注意边的编号是从1开始的,因此需要设置最初始的值为-1
		memset(inq, 0, sizeof(inq));
		memset(dfn, 0, sizeof(dfn));
		res.clear();
		dot.clear();
		while (!S.empty()) S.pop();
		tot = 0;

		cin >> b;
		addline(a, b);
		cin >> a;
		while (a) {
			cin >> b;
			if (a > n) n = a;
			if (b > n) n = b;
			addline(a, b);
			dot.insert(a);
			dot.insert(b);
			cin >> a;
		}

		//for (int i = 0; i < E.size(); i++) cout << E[i].from << " " << E[i].to << endl;

		set<int>::iterator it = dot.begin();
		for (; it != dot.end();it++) {
			if (!dfn[*it]) tarjan(*it,-1,1);
			//for (int i = h[*it]; i != -1; i=E[i].next) cout << E[i].from << " " << E[i].to << endl;
		}
		num++;
		cout << "Network #" << num << endl;
		if (!res.size()) cout << "  No SPF nodes" << endl;
		else {
			set<pair<int,int> >::iterator it = res.begin();
			for (; it!=res.end(); it++) cout << "  SPF node " << (*it).first << " leaves " << (*it).second << " subnets" << endl;
		}
		cout << endl;
		
		cin >> a;
	}
}

tip:
1.链式前向星的结构,若使用vector来存储边,则h的初始值应该设置为-1,而不是0。
2.该题要求输出的节点编号是成顺序的,因此需要使用Set或者sort一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值